msnetloader 0.0.0__tar.gz

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.
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: msnetloader
3
+ Version: 0.0.0
4
+ Summary: A Python API for seamless integration of π-MSNet into AI workflows
5
+ Author-email: chengxin dai <chengxin2024@126.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/daichengxin/MSnet/
8
+ Project-URL: Repository, https://github.com/daichengxin/MSnet/
9
+ Keywords: sdrf,python,multiomics,proteomics
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: torch==2.6.0
19
+ Requires-Dist: pandas
20
+ Requires-Dist: numpy
21
+ Requires-Dist: pyarrow==17.0.0
22
+ Requires-Dist: tensorflow
23
+ Requires-Dist: duckdb
24
+
25
+ # MSNetLoader
26
+ A Python API for seamless integration of π-MSNet into AI workflows
@@ -0,0 +1,2 @@
1
+ # MSNetLoader
2
+ A Python API for seamless integration of π-MSNet into AI workflows
@@ -0,0 +1,6 @@
1
+
2
+ __version__ = "0.0.1"
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ ]
@@ -0,0 +1,128 @@
1
+ import torch
2
+ import numpy as np
3
+ from torch.utils.data import IterableDataset
4
+ import duckdb
5
+
6
+
7
+ class DeNovoIterableDataset(IterableDataset):
8
+
9
+ def __init__(self, parquet_path, max_peaks=150, batch_size=32,
10
+ min_consensus_support=None,
11
+ max_pep=None):
12
+
13
+ con = duckdb.connect()
14
+ self.min_consensus_support = min_consensus_support
15
+ self.max_pep = max_pep
16
+
17
+ conditions = []
18
+ params = []
19
+
20
+ if self.min_consensus_support is not None:
21
+ conditions.append("consensus_support >= ?")
22
+ params.append(self.min_consensus_support)
23
+
24
+ if self.max_pep is not None:
25
+ conditions.append("posterior_error_probability <= ?")
26
+ params.append(self.max_pep)
27
+
28
+ where_clause = ""
29
+ if conditions:
30
+ where_clause = "WHERE " + " AND ".join(conditions)
31
+
32
+ query = f"""
33
+ SELECT
34
+ peptidoform,
35
+ exp_mass_to_charge AS precursor_mz,
36
+ precursor_charge AS charge,
37
+ mz_array,
38
+ consensus_support,
39
+ posterior_error_probability,
40
+ intensity_array
41
+ FROM parquet_scan(?)
42
+ {where_clause}
43
+ """
44
+
45
+ self.cursor = con.execute(query, [parquet_path] + params)
46
+
47
+ self.batch_size = batch_size
48
+ self.max_peaks = max_peaks
49
+
50
+ def __iter__(self):
51
+ reader = self.cursor.fetch_record_batch(self.batch_size)
52
+
53
+ for batch in reader:
54
+ if batch.num_rows == 0:
55
+ continue
56
+ yield self.process_batch(batch)
57
+
58
+ # -----------------------------
59
+ def process_batch(self, batch):
60
+
61
+ peptidoform = batch["peptidoform"].to_pylist()
62
+ charges = batch["charge"].to_pylist()
63
+ mz_list = batch["mz_array"].to_pylist()
64
+ int_list = batch["intensity_array"].to_pylist()
65
+ precursor_mz = batch["precursor_mz"].to_pylist()
66
+ consensus_supports = batch["consensus_support"].to_pylist()
67
+ peps = batch["posterior_error_probability"].to_pylist()
68
+
69
+ spectra_out = []
70
+ seq_out = []
71
+ charge_out = []
72
+ precursor_out = []
73
+
74
+ # -----------------------------
75
+ # per spectrum processing
76
+ # -----------------------------
77
+ for i in range(len(peptidoform)):
78
+ mz = np.asarray(mz_list[i], dtype=np.float32)
79
+ intensity = np.asarray(int_list[i], dtype=np.float32)
80
+
81
+ if len(mz) == 0:
82
+ continue
83
+
84
+ # -------------------------
85
+ # top-k peaks
86
+ # -------------------------
87
+ if len(mz) > self.max_peaks:
88
+ idx = np.argsort(intensity)[-self.max_peaks:]
89
+ mz = mz[idx]
90
+ intensity = intensity[idx]
91
+
92
+ # -------------------------
93
+ # normalize
94
+ # -------------------------
95
+ max_int = intensity.max() if len(intensity) > 0 else 1.0
96
+ intensity = intensity / max_int
97
+
98
+ spectrum = np.stack([mz, intensity], axis=1)
99
+
100
+ spectra_out.append(torch.tensor(spectrum, dtype=torch.float32))
101
+ seq_out.append(peptidoform[i])
102
+ charge_out.append(charges[i])
103
+ precursor_out.append(precursor_mz[i])
104
+
105
+ return {
106
+ "spectrum": spectra_out,
107
+ "sequence": seq_out,
108
+ "precursor_mz": torch.tensor(precursor_out, dtype=torch.float32),
109
+ "charge": torch.tensor(charge_out, dtype=torch.long),
110
+ }
111
+
112
+
113
+ if __name__ == '__main__':
114
+ """Test dataset + sampler + dataloader pipeline."""
115
+ file_path = ["D:/gitrepo/MSnet/MSNetLoader/tests/test_data/PXD014877-Akkermansia_muciniphilia-MSNet.parquet",
116
+ "D:/gitrepo/MSnet/MSNetLoader/tests/test_data/PXD014877_Clostridium_Bolteae-MSNet.parquet"]
117
+ dataset = DeNovoIterableDataset(file_path)
118
+ from torch.utils.data import DataLoader
119
+ loader = DataLoader(
120
+ dataset,
121
+ batch_size=None,
122
+ num_workers=0,
123
+ pin_memory=False
124
+ )
125
+
126
+ batch = next(iter(loader))
127
+ print(batch)
128
+
@@ -0,0 +1,143 @@
1
+ import tensorflow as tf
2
+ import numpy as np
3
+ import duckdb
4
+
5
+
6
+ class DeNovoTFDataset:
7
+
8
+ def __init__(self, parquet_path, max_peaks=150, batch_size=32,
9
+ min_consensus_support=None,
10
+ max_pep=None):
11
+
12
+ con = duckdb.connect()
13
+ self.min_consensus_support = min_consensus_support
14
+ self.max_pep = max_pep
15
+
16
+ conditions = []
17
+ params = []
18
+
19
+ if self.min_consensus_support is not None:
20
+ conditions.append("consensus_support >= ?")
21
+ params.append(self.min_consensus_support)
22
+
23
+ if self.max_pep is not None:
24
+ conditions.append("posterior_error_probability <= ?")
25
+ params.append(self.max_pep)
26
+
27
+ where_clause = ""
28
+ if conditions:
29
+ where_clause = "WHERE " + " AND ".join(conditions)
30
+
31
+ query = f"""
32
+ SELECT
33
+ peptidoform,
34
+ exp_mass_to_charge AS precursor_mz,
35
+ precursor_charge AS charge,
36
+ mz_array,
37
+ consensus_support,
38
+ posterior_error_probability,
39
+ intensity_array
40
+ FROM parquet_scan(?)
41
+ {where_clause}
42
+ """
43
+
44
+ self.cursor = con.execute(query, [parquet_path] + params)
45
+
46
+ self.batch_size = batch_size
47
+ self.max_peaks = max_peaks
48
+
49
+ # =========================================================
50
+ # Generator (核心)
51
+ # =========================================================
52
+ def generator(self):
53
+ reader = self.cursor.fetch_record_batch(self.batch_size)
54
+ for batch in reader:
55
+ if batch.num_rows == 0:
56
+ continue
57
+ result = self.process_batch(batch)
58
+ for i in range(len(result["sequence"])):
59
+ yield (
60
+ result["spectrum"][i],
61
+ result["sequence"][i],
62
+ result["precursor_mz"][i],
63
+ result["charge"][i],
64
+ )
65
+
66
+ # =========================================================
67
+ # TF Dataset接口
68
+ # =========================================================
69
+ def get_dataset(self):
70
+
71
+ output_signature = (
72
+ tf.TensorSpec(shape=(None, 2), dtype=tf.float32), # spectrum
73
+ tf.TensorSpec(shape=(), dtype=tf.string), # sequence
74
+ tf.TensorSpec(shape=(), dtype=tf.float32), # precursor_mz
75
+ tf.TensorSpec(shape=(), dtype=tf.int32), # charge
76
+ )
77
+
78
+ ds = tf.data.Dataset.from_generator(
79
+ self.generator,
80
+ output_signature=output_signature
81
+ )
82
+
83
+ return ds
84
+
85
+ # =========================================================
86
+ # batch处理(基本不变)
87
+ # =========================================================
88
+ def process_batch(self, batch):
89
+
90
+ peptidoform = batch["peptidoform"].to_pylist()
91
+ charges = batch["charge"].to_pylist()
92
+ mz_list = batch["mz_array"].to_pylist()
93
+ int_list = batch["intensity_array"].to_pylist()
94
+ precursor_mz = batch["precursor_mz"].to_pylist()
95
+ consensus_supports = batch["consensus_support"].to_pylist()
96
+ peps = batch["posterior_error_probability"].to_pylist()
97
+
98
+ spectra_out = []
99
+ seq_out = []
100
+ charge_out = []
101
+ precursor_out = []
102
+
103
+ for i in range(len(peptidoform)):
104
+
105
+ if not self.filter_by_consensus_support(consensus_supports[i]):
106
+ continue
107
+
108
+ if not self.filter_by_pep(peps[i]):
109
+ continue
110
+
111
+ mz = np.asarray(mz_list[i], dtype=np.float32)
112
+ intensity = np.asarray(int_list[i], dtype=np.float32)
113
+
114
+ if len(mz) == 0:
115
+ continue
116
+
117
+ # -------------------------
118
+ # top-k peaks
119
+ # -------------------------
120
+ if len(mz) > self.max_peaks:
121
+ idx = np.argsort(intensity)[-self.max_peaks:]
122
+ mz = mz[idx]
123
+ intensity = intensity[idx]
124
+
125
+ # -------------------------
126
+ # normalize
127
+ # -------------------------
128
+ max_int = intensity.max() if len(intensity) > 0 else 1.0
129
+ intensity = intensity / max_int
130
+
131
+ spectrum = np.stack([mz, intensity], axis=1)
132
+
133
+ spectra_out.append(spectrum.astype(np.float32))
134
+ seq_out.append(peptidoform[i].encode("utf-8")) # TF需要bytes
135
+ charge_out.append(np.int32(charges[i]))
136
+ precursor_out.append(np.float32(precursor_mz[i]))
137
+
138
+ return {
139
+ "spectrum": spectra_out,
140
+ "sequence": seq_out,
141
+ "precursor_mz": precursor_out,
142
+ "charge": charge_out,
143
+ }
@@ -0,0 +1,207 @@
1
+ import torch
2
+ import numpy as np
3
+ from torch.utils.data import IterableDataset
4
+ import duckdb
5
+ import re
6
+
7
+
8
+ class MS2TorchDataset(IterableDataset):
9
+
10
+ def __init__(self, parquet_path, batch_size=8, ion_types=("b", "y"), charges=(1, 2),
11
+ min_consensus_support=None,
12
+ max_pep=None
13
+ ):
14
+
15
+ con = duckdb.connect()
16
+ self.min_consensus_support = min_consensus_support
17
+ self.max_pep = max_pep
18
+
19
+ conditions = []
20
+ params = []
21
+
22
+ if self.min_consensus_support is not None:
23
+ conditions.append("consensus_support >= ?")
24
+ params.append(self.min_consensus_support)
25
+
26
+ if self.max_pep is not None:
27
+ conditions.append("posterior_error_probability <= ?")
28
+ params.append(self.max_pep)
29
+
30
+ where_clause = ""
31
+ if conditions:
32
+ where_clause = "WHERE " + " AND ".join(conditions)
33
+
34
+ query = f"""
35
+ SELECT
36
+ sequence,
37
+ peptidoform,
38
+ precursor_charge AS charge,
39
+ cv_params.Instrument AS instrument,
40
+ CAST(cv_params."Collision Energy" AS DOUBLE) AS nce,
41
+ ion_type_array,
42
+ charge_array,
43
+ intensity_array
44
+ FROM parquet_scan(?)
45
+ {where_clause}
46
+ ORDER BY length(sequence)
47
+ """
48
+
49
+ self.cursor = con.execute(query, [parquet_path] + params)
50
+ self.batch_size = batch_size
51
+
52
+ self.ion_types = set(ion_types)
53
+ self.charges = set(charges)
54
+
55
+ self.channel_map = {
56
+ ("b", 1): 0,
57
+ ("b", 2): 1,
58
+ ("y", 1): 2,
59
+ ("y", 2): 3,
60
+ }
61
+
62
+ self.active_channels = [
63
+ self.channel_map[(t, z)]
64
+ for t in ion_types
65
+ for z in charges
66
+ if (t, z) in self.channel_map
67
+ ]
68
+
69
+ # -----------------------------
70
+ def __iter__(self):
71
+ reader = self.cursor.fetch_record_batch(self.batch_size)
72
+
73
+ for batch in reader:
74
+ if batch.num_rows == 0:
75
+ continue
76
+ yield self.process_batch(batch)
77
+
78
+ # -----------------------------
79
+ def process_batch(self, batch):
80
+ sequences = batch["sequence"].to_pylist()
81
+ peptidoform = batch["peptidoform"].to_pylist()
82
+ charges = batch["charge"].to_pylist()
83
+ nces = batch["nce"].to_pylist()
84
+ instruments = batch["instrument"].to_pylist()
85
+ fragments = batch["ion_type_array"].to_pylist()
86
+ fragment_charges = batch["charge_array"].to_pylist()
87
+ intensities = batch["intensity_array"].to_pylist()
88
+
89
+ targets = self.build_batch_fragments(
90
+ sequences,
91
+ fragments,
92
+ fragment_charges,
93
+ intensities
94
+ )
95
+
96
+ charge_tensor = torch.tensor(charges, dtype=torch.long)
97
+ nce_tensor = torch.tensor(nces, dtype=torch.float32)
98
+
99
+ return {
100
+ "peptide": peptidoform,
101
+ "charge": charge_tensor,
102
+ "nce": nce_tensor,
103
+ "instruments": instruments,
104
+ "targets": targets
105
+ }
106
+
107
+ # -----------------------------
108
+ def build_batch_fragments(
109
+ self,
110
+ sequences,
111
+ fragments_list,
112
+ frag_charges_list,
113
+ intensity_list
114
+ ):
115
+
116
+ B = len(sequences)
117
+ Lmax = max(len(s) for s in sequences)
118
+
119
+ out = np.zeros((B, Lmax - 1, 4), dtype=np.float32)
120
+
121
+ # -----------------------------
122
+ for b in range(B):
123
+
124
+ ions = fragments_list[b]
125
+ charges = frag_charges_list[b]
126
+ ints = intensity_list[b]
127
+
128
+ if len(ions) == 0:
129
+ continue
130
+
131
+ ions = np.asarray(ions)
132
+ charges = np.asarray(charges)
133
+ ints = np.asarray(ints, dtype=np.float32)
134
+
135
+ valid = (ions != None)
136
+ ions = ions[valid]
137
+ charges = charges[valid]
138
+ ints = ints[valid]
139
+
140
+ if len(ions) == 0:
141
+ continue
142
+
143
+ # remove neutral loss
144
+ mask = np.char.find(ions.astype(str), "-") == -1
145
+ ions = ions[mask]
146
+ charges = charges[mask]
147
+ ints = ints[mask]
148
+
149
+ if len(ions) == 0:
150
+ continue
151
+
152
+ # -----------------------------
153
+ # parse ion string safely
154
+ # -----------------------------
155
+ ion_str = ions.astype(str)
156
+
157
+ ion_type = np.array([x[0] for x in ion_str])
158
+
159
+ # SAFE regex position parsing
160
+ pos = np.array([
161
+ int(re.findall(r"\d+", x)[0]) if re.findall(r"\d+", x) else -1
162
+ for x in ion_str
163
+ ])
164
+
165
+ seq_len = len(sequences[b])
166
+
167
+ valid = (pos >= 1) & (pos < seq_len)
168
+
169
+ ion_type = ion_type[valid]
170
+ pos = pos[valid] - 1
171
+ charges = charges[valid]
172
+ ints = ints[valid]
173
+
174
+ if len(pos) == 0:
175
+ continue
176
+
177
+ # -----------------------------
178
+ # channel mapping (fixed)
179
+ # -----------------------------
180
+ ch = np.full(len(ion_type), -1, dtype=np.int32)
181
+
182
+ for (t, z), c in self.channel_map.items():
183
+ if t in self.ion_types and z in self.charges:
184
+ ch[(ion_type == t) & (charges == z)] = c
185
+
186
+ valid_ch = ch >= 0
187
+
188
+ pos = pos[valid_ch]
189
+ ch = ch[valid_ch]
190
+ ints = ints[valid_ch]
191
+
192
+ if len(pos) == 0:
193
+ continue
194
+
195
+ # -----------------------------
196
+ # intensity normalize
197
+ # -----------------------------
198
+ max_int = ints.max() if len(ints) > 0 else 1.0
199
+ ints = ints / max_int
200
+
201
+ # -----------------------------
202
+ # scatter
203
+ # -----------------------------
204
+ out[b, pos, ch] += ints
205
+
206
+ # -----------------------------
207
+ return torch.from_numpy(out[:, :, self.active_channels])