pypsmcbor 0.0.3__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.
- pypsmcbor/__init__.py +37 -0
- pypsmcbor/cbor2/__init__.py +1 -0
- pypsmcbor/cbor2/cborstreamdec.py +274 -0
- pypsmcbor/comparsample.py +72 -0
- pypsmcbor/data/typst/global_report.typ +59 -0
- pypsmcbor/data/typst/mz_delta_plot.typ +118 -0
- pypsmcbor/data/typst/psm_report.typ +14 -0
- pypsmcbor/data/typst/psm_report_svg.typ +16 -0
- pypsmcbor/enums.py +42 -0
- pypsmcbor/fastasequence.py +20 -0
- pypsmcbor/harvest/__init__.py +1 -0
- pypsmcbor/harvest/harvest_psmcbor.py +86 -0
- pypsmcbor/identificationdataset.py +97 -0
- pypsmcbor/ods/__init__.py +0 -0
- pypsmcbor/ods/ods_all_tables.py +43 -0
- pypsmcbor/ods/ods_informations.py +46 -0
- pypsmcbor/ods/ods_ms_delta.py +52 -0
- pypsmcbor/ods/ods_protein.py +39 -0
- pypsmcbor/ods/ods_sample_scan.py +218 -0
- pypsmcbor/peaks/__init__.py +2 -0
- pypsmcbor/peaks/peakscomparsample.py +58 -0
- pypsmcbor/peaks/peakspeptidepsm.py +139 -0
- pypsmcbor/peaks/peakspeptidesample.py +82 -0
- pypsmcbor/peaks/peakspeptidescan.py +136 -0
- pypsmcbor/peaks/peakspeptidetable.py +101 -0
- pypsmcbor/peaks/peaksscanpair.py +52 -0
- pypsmcbor/proforma/__init__.py +1 -0
- pypsmcbor/proforma/proforma.py +79 -0
- pypsmcbor/protein.py +77 -0
- pypsmcbor/proteinmap.py +78 -0
- pypsmcbor/proteinref.py +62 -0
- pypsmcbor/psm.py +214 -0
- pypsmcbor/sample.py +145 -0
- pypsmcbor/scan.py +237 -0
- pypsmcbor/scanid.py +41 -0
- pypsmcbor/scanpair.py +74 -0
- pypsmcbor/stream/__init__.py +3 -0
- pypsmcbor/stream/ms2pipeval.py +58 -0
- pypsmcbor/stream/proformamodreplace.py +104 -0
- pypsmcbor/stream/psmcborpsmfilter.py +117 -0
- pypsmcbor/stream/psmcborreplacer.py +41 -0
- pypsmcbor/stream/psmcborstreamin.py +24 -0
- pypsmcbor/stream/psmcborstreamout.py +310 -0
- pypsmcbor/typst/__init__.py +0 -0
- pypsmcbor/typst/json_scan_report.py +174 -0
- pypsmcbor/utils/__init__.py +0 -0
- pypsmcbor/utils/aa_mass_delta_distribution.py +66 -0
- pypsmcbor/utils/ms_delta.py +92 -0
- pypsmcbor/utils/sample_ms2_distribution.py +54 -0
- pypsmcbor-0.0.3.dist-info/METADATA +14 -0
- pypsmcbor-0.0.3.dist-info/RECORD +54 -0
- pypsmcbor-0.0.3.dist-info/WHEEL +5 -0
- pypsmcbor-0.0.3.dist-info/licenses/LICENSE +232 -0
- pypsmcbor-0.0.3.dist-info/top_level.txt +1 -0
pypsmcbor/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# This file is part of pypsmcbor.
|
|
2
|
+
#
|
|
3
|
+
# pypsmcbor is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# pypsmcbor is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with pypsmcbor. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
from .comparsample import (
|
|
17
|
+
ComparSampleMatchingScanIndex,
|
|
18
|
+
ComparSampleMatchingScanNativeId,
|
|
19
|
+
)
|
|
20
|
+
from .enums import MzPrecisionUnit, PeaksEngine, PsmCborMass, PsmCborSoftware
|
|
21
|
+
from .fastasequence import FastaSequence
|
|
22
|
+
from .harvest.harvest_psmcbor import HarvestPsmCbor
|
|
23
|
+
from .identificationdataset import IdentificationDataSet
|
|
24
|
+
from .peaks.peakscomparsample import PeaksComparSampleMatchingScan
|
|
25
|
+
from .peaks.peakspeptidetable import PeaksPeptideTable
|
|
26
|
+
from .proforma import ProForma
|
|
27
|
+
from .protein import Protein
|
|
28
|
+
from .proteinmap import ProteinMap
|
|
29
|
+
from .psm import Psm
|
|
30
|
+
from .sample import Sample
|
|
31
|
+
from .scan import Scan
|
|
32
|
+
from .scanid import ScanId
|
|
33
|
+
from .scanpair import ScanPair
|
|
34
|
+
from .stream.proformamodreplace import ProFormaModReplace
|
|
35
|
+
from .stream.psmcborpsmfilter import PsmCborPsmFilter
|
|
36
|
+
from .stream.psmcborstreamin import PsmCborStreamIn
|
|
37
|
+
from .stream.psmcborstreamout import PsmCborStreamOut
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cborstreamdec import CborStreamDec
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# This file is part of pypsmcbor.
|
|
2
|
+
#
|
|
3
|
+
# pypsmcbor is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# pypsmcbor is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with pypsmcbor. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import struct
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import IO, Any, cast
|
|
21
|
+
|
|
22
|
+
from cbor2 import CBORDecoder
|
|
23
|
+
from cbor2._types import CBORDecodeValueError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ContainerEnd:
|
|
28
|
+
end_is_reached: bool
|
|
29
|
+
remaining_items: int | None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CborStreamDec:
|
|
33
|
+
def __init__(self, fp: IO[bytes]):
|
|
34
|
+
self._fp = fp
|
|
35
|
+
self._cbor_decoder = CBORDecoder(fp)
|
|
36
|
+
self._initial_byte = None
|
|
37
|
+
self._major_type = 0
|
|
38
|
+
self._subtype = 0
|
|
39
|
+
self._length = None
|
|
40
|
+
self._container_stack = []
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def length(self) -> int | None:
|
|
44
|
+
return self._length
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def expected_string(self) -> str:
|
|
48
|
+
if self._container_stack[-1].end_is_reached:
|
|
49
|
+
raise CBORDecodeValueError(f"end of container reached, use leave_container")
|
|
50
|
+
if self._initial_byte is None:
|
|
51
|
+
self._read_next_initial_byte()
|
|
52
|
+
if self.is_string():
|
|
53
|
+
the_string = self._cbor_decoder.decode_string(self._subtype)
|
|
54
|
+
self._initial_byte = None
|
|
55
|
+
self._check_container_remaining_items()
|
|
56
|
+
return the_string
|
|
57
|
+
else:
|
|
58
|
+
raise CBORDecodeValueError(
|
|
59
|
+
f"next value is not a string 0x{self._major_type:x}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> Any:
|
|
63
|
+
"""Use the internal cbor2 decoder to return the whole data as a dictionnary or array"""
|
|
64
|
+
if self._container_stack[-1].end_is_reached:
|
|
65
|
+
raise CBORDecodeValueError("end of container reached, use leave_container")
|
|
66
|
+
if self._initial_byte is None:
|
|
67
|
+
self._read_next_initial_byte()
|
|
68
|
+
if self.is_map():
|
|
69
|
+
if self.has_next():
|
|
70
|
+
the_map = self._cbor_decoder.decode_map(self._subtype)
|
|
71
|
+
self._initial_byte = None
|
|
72
|
+
self._check_container_remaining_items()
|
|
73
|
+
return the_map
|
|
74
|
+
elif self.is_array():
|
|
75
|
+
if self.has_next():
|
|
76
|
+
the_map = self._cbor_decoder.decode_array(self._subtype)
|
|
77
|
+
self._initial_byte = None
|
|
78
|
+
self._check_container_remaining_items()
|
|
79
|
+
return the_map
|
|
80
|
+
elif self._container_stack[-1].end_is_reached:
|
|
81
|
+
raise CBORDecodeValueError("end of container reached, use leave_container")
|
|
82
|
+
raise CBORDecodeValueError("the value is not a dict nor an array")
|
|
83
|
+
|
|
84
|
+
def value(self) -> str | int | bytes | float | None:
|
|
85
|
+
# 0: CBORDecoder.decode_uint,
|
|
86
|
+
# 1: CBORDecoder.decode_negint,
|
|
87
|
+
# 2: CBORDecoder.decode_bytestring,
|
|
88
|
+
# 3: CBORDecoder.decode_string,
|
|
89
|
+
# 4: CBORDecoder.decode_array,
|
|
90
|
+
# 5: CBORDecoder.decode_map,
|
|
91
|
+
# 6: CBORDecoder.decode_semantic,
|
|
92
|
+
# 7: CBORDecoder.decode_special,
|
|
93
|
+
if self._container_stack[-1].end_is_reached:
|
|
94
|
+
raise CBORDecodeValueError(f"end of container reached, use leave_container")
|
|
95
|
+
if self._initial_byte is None:
|
|
96
|
+
self._read_next_initial_byte()
|
|
97
|
+
if self.is_string():
|
|
98
|
+
the_string = self._cbor_decoder.decode_string(self._subtype)
|
|
99
|
+
self._initial_byte = None
|
|
100
|
+
self._check_container_remaining_items()
|
|
101
|
+
return the_string
|
|
102
|
+
elif self.is_uint():
|
|
103
|
+
the_uint = self._cbor_decoder.decode_uint(self._subtype)
|
|
104
|
+
self._initial_byte = None
|
|
105
|
+
self._check_container_remaining_items()
|
|
106
|
+
return the_uint
|
|
107
|
+
elif self.is_negint():
|
|
108
|
+
the_negint = self._cbor_decoder.decode_negint(self._subtype)
|
|
109
|
+
self._initial_byte = None
|
|
110
|
+
self._check_container_remaining_items()
|
|
111
|
+
return the_negint
|
|
112
|
+
elif self.is_bytestring():
|
|
113
|
+
the_bytestr = self._cbor_decoder.decode_bytestring(self._subtype)
|
|
114
|
+
self._initial_byte = None
|
|
115
|
+
self._check_container_remaining_items()
|
|
116
|
+
return the_bytestr
|
|
117
|
+
elif self._major_type == 7:
|
|
118
|
+
if self.has_next():
|
|
119
|
+
the_special = self._cbor_decoder.decode_special(self._subtype)
|
|
120
|
+
self._initial_byte = None
|
|
121
|
+
self._check_container_remaining_items()
|
|
122
|
+
return the_special
|
|
123
|
+
elif self.is_container():
|
|
124
|
+
raise CBORDecodeValueError(
|
|
125
|
+
"the next value is a container, use enter_container or to_dict to proceed"
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
raise CBORDecodeValueError(
|
|
129
|
+
f"next value is not taken into account 0x{self._major_type:x}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _check_container_remaining_items(self):
|
|
133
|
+
if len(self._container_stack) > 0:
|
|
134
|
+
item_number = self._container_stack[-1].remaining_items
|
|
135
|
+
if item_number is not None:
|
|
136
|
+
self._container_stack[-1].remaining_items = item_number - 1
|
|
137
|
+
if self._container_stack[-1].remaining_items == 0:
|
|
138
|
+
self._container_stack[-1].end_is_reached = True
|
|
139
|
+
|
|
140
|
+
def has_next(self) -> bool:
|
|
141
|
+
# print(f"1next major type is 0x{self._major_type:x} length{self._length}")
|
|
142
|
+
if self._container_stack[-1].end_is_reached:
|
|
143
|
+
return False
|
|
144
|
+
if self._initial_byte is None:
|
|
145
|
+
self._read_next_initial_byte()
|
|
146
|
+
# print(f"next major type is 0x{self._major_type:x}")
|
|
147
|
+
if self._major_type == 7:
|
|
148
|
+
if self._subtype == 31:
|
|
149
|
+
# print("special subtype", self._subtype)
|
|
150
|
+
self._initial_byte = None
|
|
151
|
+
self._container_stack[-1].end_is_reached = True
|
|
152
|
+
return False
|
|
153
|
+
else:
|
|
154
|
+
pass
|
|
155
|
+
# print("special subtype", self._subtype)
|
|
156
|
+
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def next(self) -> bool:
|
|
160
|
+
"""Advance the CBOR stream decoding one element. You should usually call this function when parsing fixed-width basic elements (that is, integers, simple values, tags and floating point values). But this function can be called when the current item is a string, array or map too and it will skip over that entire element, including all contained elements."""
|
|
161
|
+
if self._container_stack[-1].end_is_reached:
|
|
162
|
+
return False
|
|
163
|
+
self._cbor_decoder.decode()
|
|
164
|
+
self._initial_byte = None
|
|
165
|
+
self._check_container_remaining_items()
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
def _read_next_initial_byte(self):
|
|
169
|
+
self._initial_byte = self._cbor_decoder.read(1)[0]
|
|
170
|
+
self._major_type = self._initial_byte >> 5
|
|
171
|
+
self._subtype = self._initial_byte & 31
|
|
172
|
+
self._length = None
|
|
173
|
+
|
|
174
|
+
def is_array(self) -> bool:
|
|
175
|
+
# 0: CBORDecoder.decode_uint,
|
|
176
|
+
# 1: CBORDecoder.decode_negint,
|
|
177
|
+
# 2: CBORDecoder.decode_bytestring,
|
|
178
|
+
# 3: CBORDecoder.decode_string,
|
|
179
|
+
# 4: CBORDecoder.decode_array,
|
|
180
|
+
# 5: CBORDecoder.decode_map,
|
|
181
|
+
# 6: CBORDecoder.decode_semantic,
|
|
182
|
+
# 7: CBORDecoder.decode_special,
|
|
183
|
+
if self._initial_byte is None:
|
|
184
|
+
self._read_next_initial_byte()
|
|
185
|
+
if self._major_type == 4:
|
|
186
|
+
return True
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
def is_negint(self) -> bool:
|
|
190
|
+
if self._initial_byte is None:
|
|
191
|
+
self._read_next_initial_byte()
|
|
192
|
+
if self._major_type == 1:
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
def is_bytestring(self) -> bool:
|
|
197
|
+
if self._initial_byte is None:
|
|
198
|
+
self._read_next_initial_byte()
|
|
199
|
+
if self._major_type == 2:
|
|
200
|
+
return True
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def is_uint(self) -> bool:
|
|
204
|
+
if self._initial_byte is None:
|
|
205
|
+
self._read_next_initial_byte()
|
|
206
|
+
if self._major_type == 0:
|
|
207
|
+
return True
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def is_string(self) -> bool:
|
|
211
|
+
if self._initial_byte is None:
|
|
212
|
+
self._read_next_initial_byte()
|
|
213
|
+
if self._major_type == 3:
|
|
214
|
+
return True
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def is_map(self) -> bool:
|
|
218
|
+
if self._initial_byte is None:
|
|
219
|
+
self._read_next_initial_byte()
|
|
220
|
+
if self._major_type == 5:
|
|
221
|
+
return True
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def is_container(self) -> bool:
|
|
225
|
+
if self.is_array() or self.is_map():
|
|
226
|
+
return True
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def leave_container(self) -> bool:
|
|
230
|
+
if self._container_stack[-1].end_is_reached:
|
|
231
|
+
self._container_stack.pop()
|
|
232
|
+
return True
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
def enter_container(self) -> bool:
|
|
236
|
+
if self.is_container():
|
|
237
|
+
self._check_container_remaining_items()
|
|
238
|
+
self._length = self._decode_length(self._subtype, allow_indefinite=True)
|
|
239
|
+
items = self._length
|
|
240
|
+
if items is not None:
|
|
241
|
+
if self.is_map():
|
|
242
|
+
items = items * 2
|
|
243
|
+
self._initial_byte = None
|
|
244
|
+
c_end = ContainerEnd(end_is_reached=False, remaining_items=items)
|
|
245
|
+
self._container_stack.append(c_end)
|
|
246
|
+
if self._length is None:
|
|
247
|
+
return True
|
|
248
|
+
else:
|
|
249
|
+
if self._length > sys.maxsize:
|
|
250
|
+
raise CBORDecodeValueError(
|
|
251
|
+
f"invalid length for array 0x{self._length:x}"
|
|
252
|
+
)
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
def _decode_length(
|
|
257
|
+
self, subtype: int, allow_indefinite: bool = False
|
|
258
|
+
) -> int | None:
|
|
259
|
+
if subtype < 24:
|
|
260
|
+
return subtype
|
|
261
|
+
elif subtype == 24:
|
|
262
|
+
return self._cbor_decoder.read(1)[0]
|
|
263
|
+
elif subtype == 25:
|
|
264
|
+
return cast(int, struct.unpack(">H", self._cbor_decoder.read(2))[0])
|
|
265
|
+
elif subtype == 26:
|
|
266
|
+
return cast(int, struct.unpack(">L", self._cbor_decoder.read(4))[0])
|
|
267
|
+
elif subtype == 27:
|
|
268
|
+
return cast(int, struct.unpack(">Q", self._cbor_decoder.read(8))[0])
|
|
269
|
+
elif subtype == 31 and allow_indefinite:
|
|
270
|
+
return None
|
|
271
|
+
else:
|
|
272
|
+
raise CBORDecodeValueError(
|
|
273
|
+
f"unknown unsigned integer subtype 0x{subtype:x}"
|
|
274
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# This file is part of pypsmcbor.
|
|
2
|
+
#
|
|
3
|
+
# pypsmcbor is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# pypsmcbor is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with pypsmcbor. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from .sample import Sample
|
|
18
|
+
from .scanpair import ScanPair
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ComparSampleMatchingScanBase(object):
|
|
22
|
+
def __init__(self, sample_data1: Sample, sample_data2: Sample, compar_scan):
|
|
23
|
+
self.scan_pair_list = []
|
|
24
|
+
for scan_data1 in sample_data1.scan_list:
|
|
25
|
+
for scan_data2 in sample_data2.scan_list:
|
|
26
|
+
if compar_scan(scan_data1, scan_data2):
|
|
27
|
+
self.scan_pair_list.append(ScanPair(scan_data1, scan_data2))
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def count_scan_pair(self) -> int:
|
|
31
|
+
return len(self.scan_pair_list)
|
|
32
|
+
|
|
33
|
+
def find_if(self, check_scanpair) -> list[ScanPair]:
|
|
34
|
+
"""find scanpair that respect the check_scanpair conditions
|
|
35
|
+
:param check_scanpair: a function accepting a scanpair as argument
|
|
36
|
+
and return true if conditions are met"""
|
|
37
|
+
scan_pair_list = []
|
|
38
|
+
for scan_pair in self.scan_pair_list:
|
|
39
|
+
if check_scanpair(scan_pair):
|
|
40
|
+
scan_pair_list.append(scan_pair)
|
|
41
|
+
return scan_pair_list
|
|
42
|
+
|
|
43
|
+
def count_if(self, check_scanpair) -> int:
|
|
44
|
+
"""count scanpair that respect the check_scanpair conditions
|
|
45
|
+
:param check_scanpair: a function accepting a scanpair as argument
|
|
46
|
+
and return true if conditions are met"""
|
|
47
|
+
count = 0
|
|
48
|
+
for scan_pair in self.scan_pair_list:
|
|
49
|
+
if check_scanpair(scan_pair):
|
|
50
|
+
count = count + 1
|
|
51
|
+
return count
|
|
52
|
+
|
|
53
|
+
def count_scan_containing_similar_sequence(self) -> int:
|
|
54
|
+
return self.count_if(lambda scan_pair: scan_pair.has_similar_sequence())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ComparSampleMatchingScanNativeId(ComparSampleMatchingScanBase):
|
|
58
|
+
def __init__(self, sample_data1: Sample, sample_data2: Sample):
|
|
59
|
+
super().__init__(
|
|
60
|
+
sample_data1,
|
|
61
|
+
sample_data2,
|
|
62
|
+
lambda scan1, scan2: scan1.id.native_id == scan2.id.native_id,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ComparSampleMatchingScanIndex(ComparSampleMatchingScanBase):
|
|
67
|
+
def __init__(self, sample_data1: Sample, sample_data2: Sample):
|
|
68
|
+
super().__init__(
|
|
69
|
+
sample_data1,
|
|
70
|
+
sample_data2,
|
|
71
|
+
lambda scan1, scan2: scan1.has_index(scan2.id.index),
|
|
72
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#import "@preview/proteograph:0.2.2": *
|
|
2
|
+
|
|
3
|
+
#import "mz_delta_plot.typ": aa-mass-delta-plot, mz-delta-plot
|
|
4
|
+
|
|
5
|
+
= Global report
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
#let psm_json_global = json("psm_data.json").global
|
|
10
|
+
|
|
11
|
+
== #psm_json_global.samples.keys().len() samples
|
|
12
|
+
|
|
13
|
+
#for (name, sample) in psm_json_global.samples.pairs() {
|
|
14
|
+
terms.item(name, [#sample.peaklist_file.name
|
|
15
|
+
#if name in psm_json_global.mzstats {
|
|
16
|
+
let count_usable_scans = 0
|
|
17
|
+
if "tic" in psm_json_global.mzstats.at(name) {
|
|
18
|
+
let xic0 = psm_json_global.mzstats.at(name).at("tic")
|
|
19
|
+
xic0.insert("TIC", "TIC")
|
|
20
|
+
|
|
21
|
+
xic-plot(
|
|
22
|
+
height: 10cm,
|
|
23
|
+
title: "Total Ion Count",
|
|
24
|
+
xic0,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
if "ms_level_count" in psm_json_global.mzstats.at(name) {
|
|
28
|
+
let ms_level_count = psm_json_global.mzstats.at(name).at("ms_level_count")
|
|
29
|
+
for (ms_level, count) in ms_level_count.pairs() {
|
|
30
|
+
terms.item([MS level #ms_level], [#count scans])
|
|
31
|
+
if int(ms_level) > 1 {
|
|
32
|
+
count_usable_scans += count
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if "psm_count" in psm_json_global.mzstats.at(name) {
|
|
38
|
+
let psm_count = psm_json_global.mzstats.at(name).at("psm_count")
|
|
39
|
+
terms.item(
|
|
40
|
+
[Coverage],
|
|
41
|
+
[#calc.round(psm_count.scans * 100 / count_usable_scans, digits: 2)% #psm_count.scans assigned on #count_usable_scans scans],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
terms.item(
|
|
45
|
+
[PSM FDR],
|
|
46
|
+
[#calc.round((psm_count.psms - psm_count.target_psms) * 100 / psm_count.psms, digits: 2)% (total PSMs: #psm_count.psms)],
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
])
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#mz-delta-plot(psm_json_global.ppm_delta)
|
|
54
|
+
|
|
55
|
+
#mz-delta-plot(psm_json_global.dalton_delta, unit: "dalton")
|
|
56
|
+
|
|
57
|
+
#if "aa_mass_delta" in psm_json_global {
|
|
58
|
+
aa-mass-delta-plot(psm_json_global.aa_mass_delta)
|
|
59
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#import "@preview/lilaq:0.6.0" as lq
|
|
2
|
+
|
|
3
|
+
#let mz-delta-diagram(unit: "ppm") = it => {
|
|
4
|
+
show: lq.set-diagram(
|
|
5
|
+
xlabel: [$m/z$ #sym.Delta (observed - theoretical) in #unit],
|
|
6
|
+
ylabel: [PSM count],
|
|
7
|
+
legend: none,
|
|
8
|
+
xaxis: (mirror: none),
|
|
9
|
+
yaxis: (mirror: none),
|
|
10
|
+
)
|
|
11
|
+
it
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/// Generates a XIC plot.
|
|
16
|
+
/// -> content
|
|
17
|
+
#let mz-delta-plot(
|
|
18
|
+
/// The width of the diagram. This can be
|
|
19
|
+
/// - A `length`; in this case, it defines just the width of the data area,
|
|
20
|
+
/// excluding axes, labels, title etc.
|
|
21
|
+
/// - A `ratio` or `relative` where the ratio part is relative to the width
|
|
22
|
+
/// of the parent that the diagram is placed in. This is not allowed if the
|
|
23
|
+
/// parent has an unbounded width, e.g., a page with `width: auto`.
|
|
24
|
+
/// -> length | relative
|
|
25
|
+
width: 15cm,
|
|
26
|
+
/// The height of the diagram. This can be
|
|
27
|
+
/// - A `length`; in this case, it defines just the height of the data area,
|
|
28
|
+
/// excluding axes, labels, title etc.
|
|
29
|
+
/// - A `ratio` or `relative` where the ratio part is relative to the height
|
|
30
|
+
/// of the parent that the diagram is placed in. This is not allowed if the
|
|
31
|
+
/// parent has an unbounded height, e.g., a page with `height: auto`.
|
|
32
|
+
/// -> length | relative
|
|
33
|
+
height: 10cm,
|
|
34
|
+
/// Graph title
|
|
35
|
+
/// -> content
|
|
36
|
+
title: none,
|
|
37
|
+
unit: "ppm",
|
|
38
|
+
/// dictionary containing delta histogram
|
|
39
|
+
delta,
|
|
40
|
+
) = {
|
|
41
|
+
show: lq.cond-set(lq.grid.with(kind: "x"), stroke: orange.lighten(50%))
|
|
42
|
+
show: lq.cond-set(lq.grid.with(kind: "y"), stroke: none)
|
|
43
|
+
show: mz-delta-diagram(unit: unit)
|
|
44
|
+
|
|
45
|
+
let color-cycle = lq.color.map.petroff6
|
|
46
|
+
|
|
47
|
+
let ylimit = auto
|
|
48
|
+
|
|
49
|
+
lq.diagram(
|
|
50
|
+
width: width,
|
|
51
|
+
height: height,
|
|
52
|
+
title: title,
|
|
53
|
+
lq.bar(
|
|
54
|
+
delta.interval,
|
|
55
|
+
delta.count,
|
|
56
|
+
),
|
|
57
|
+
|
|
58
|
+
lq.place(10%, 10%, box(baseline: top, [#set align(left)
|
|
59
|
+
/ std dev: $#calc.round(delta.stddev, digits: 2)$
|
|
60
|
+
/ mean: $#if delta.mean > 0 { sym.plus }#calc.round(delta.mean, digits: 2)$
|
|
61
|
+
/ median: $#if delta.median > 0 { sym.plus }#calc.round(delta.median, digits: 2)$
|
|
62
|
+
])),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#let aa-mass-delta-plot(
|
|
67
|
+
/// The width of the diagram. This can be
|
|
68
|
+
/// - A `length`; in this case, it defines just the width of the data area,
|
|
69
|
+
/// excluding axes, labels, title etc.
|
|
70
|
+
/// - A `ratio` or `relative` where the ratio part is relative to the width
|
|
71
|
+
/// of the parent that the diagram is placed in. This is not allowed if the
|
|
72
|
+
/// parent has an unbounded width, e.g., a page with `width: auto`.
|
|
73
|
+
/// -> length | relative
|
|
74
|
+
width: 15cm,
|
|
75
|
+
/// The height of the diagram. This can be
|
|
76
|
+
/// - A `length`; in this case, it defines just the height of the data area,
|
|
77
|
+
/// excluding axes, labels, title etc.
|
|
78
|
+
/// - A `ratio` or `relative` where the ratio part is relative to the height
|
|
79
|
+
/// of the parent that the diagram is placed in. This is not allowed if the
|
|
80
|
+
/// parent has an unbounded height, e.g., a page with `height: auto`.
|
|
81
|
+
/// -> length | relative
|
|
82
|
+
height: 10cm,
|
|
83
|
+
/// Graph title
|
|
84
|
+
/// -> content
|
|
85
|
+
title: none,
|
|
86
|
+
/// dictionary containing delta histogram
|
|
87
|
+
delta,
|
|
88
|
+
) = {
|
|
89
|
+
show: lq.cond-set(lq.grid.with(kind: "y"), stroke: none)
|
|
90
|
+
show: lq.show_(
|
|
91
|
+
lq.tick-label.with(kind: "x"),
|
|
92
|
+
it => scale(x: 50%, y: 50%, reflow: true, box(
|
|
93
|
+
width: 0pt,
|
|
94
|
+
align(right, rotate(-45deg, reflow: true, it)),
|
|
95
|
+
)),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
let ylimit = auto
|
|
99
|
+
|
|
100
|
+
lq.diagram(
|
|
101
|
+
width: width,
|
|
102
|
+
height: height,
|
|
103
|
+
title: "most frequent AA mass delta",
|
|
104
|
+
xlim: (0, 60),
|
|
105
|
+
xaxis: (
|
|
106
|
+
ticks: delta.interval.map(massf => str(massf)).enumerate(),
|
|
107
|
+
subticks: none,
|
|
108
|
+
),
|
|
109
|
+
yaxis: (
|
|
110
|
+
label: [count],
|
|
111
|
+
auto-exponent-threshold: 5,
|
|
112
|
+
),
|
|
113
|
+
lq.bar(
|
|
114
|
+
range(delta.interval.len()),
|
|
115
|
+
delta.count,
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import "@preview/proteograph:0.2.4": *
|
|
2
|
+
|
|
3
|
+
#let psm_json = json("psm_data.json")
|
|
4
|
+
|
|
5
|
+
#if "global" in psm_json {
|
|
6
|
+
include "global_report.typ"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
#if psm_json.scan_arr.len() > 0 {
|
|
11
|
+
heading([PSM scan report])
|
|
12
|
+
|
|
13
|
+
for scan in psm_json.scan_arr { psm-cbor-scan-report(scan, protein-dict: psm_json.protein_map) }
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import "@preview/proteograph:0.2.4": *
|
|
2
|
+
|
|
3
|
+
#set page(height: auto, width: auto, margin: 1em)
|
|
4
|
+
|
|
5
|
+
#let psm_json = json("psm_data.json")
|
|
6
|
+
|
|
7
|
+
#if "global" in psm_json {
|
|
8
|
+
include "global_report.typ"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
#if psm_json.scan_arr.len() > 0 {
|
|
13
|
+
heading([PSM scan report])
|
|
14
|
+
|
|
15
|
+
for scan in psm_json.scan_arr { psm-cbor-scan-report(scan, protein-dict: psm_json.protein_map) }
|
|
16
|
+
}
|
pypsmcbor/enums.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# This file is part of pypsmcbor.
|
|
2
|
+
#
|
|
3
|
+
# pypsmcbor is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# pypsmcbor is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with pypsmcbor. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
from enum import Enum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MzPrecisionUnit(Enum):
|
|
20
|
+
ppm = "ppm"
|
|
21
|
+
dalton = "dalton"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PeaksEngine(Enum):
|
|
25
|
+
db_search = "DB Search"
|
|
26
|
+
deepnovo = "DeepNovo"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PsmCborSoftware(Enum):
|
|
30
|
+
grouping = "grouping"
|
|
31
|
+
sage = "sage"
|
|
32
|
+
spoms = "spoms"
|
|
33
|
+
features = "features"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PsmCborMass(float, Enum):
|
|
37
|
+
MHPLUS = 1.007276466879 # The (monoisotopic) mass of the H+ ion
|
|
38
|
+
MPROTIUM = 1.007825032241 # The (monoisotopic) mass of the H atom
|
|
39
|
+
ONEMILLION = 1000000
|
|
40
|
+
MASSOXYGEN = 15.99491461956
|
|
41
|
+
MASSCARBON = 12.0
|
|
42
|
+
MASSNITROGEN = 14.0030740048
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This file is part of pypsmcbor.
|
|
2
|
+
#
|
|
3
|
+
# pypsmcbor is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# pypsmcbor is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with pypsmcbor. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FastaSequence:
|
|
18
|
+
def __init__(self, header: str, sequence: str):
|
|
19
|
+
self.header = header
|
|
20
|
+
self.sequence = sequence
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .harvest_psmcbor import HarvestPsmCbor
|