isodec 2.0.0b2__py3-none-win_amd64.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.
Files changed (74) hide show
  1. isodec/__init__.py +8 -0
  2. isodec/__main__.py +45 -0
  3. isodec/_version.py +3 -0
  4. isodec/altdecon.py +127 -0
  5. isodec/bin/isodeclib.dll +0 -0
  6. isodec/bin/isogen.dll +0 -0
  7. isodec/bin/libmmd.dll +0 -0
  8. isodec/bin/svml_dispmd.dll +0 -0
  9. isodec/bin_to_header.py +56 -0
  10. isodec/c_interface.py +434 -0
  11. isodec/compare_tools.py +685 -0
  12. isodec/config.py +61 -0
  13. isodec/datatools.py +454 -0
  14. isodec/encoding.py +302 -0
  15. isodec/encoding_profile.py +257 -0
  16. isodec/engine.py +952 -0
  17. isodec/fwhm.py +121 -0
  18. isodec/io.py +79 -0
  19. isodec/isotope.py +83 -0
  20. isodec/match.py +1192 -0
  21. isodec/modelparams/phase_model_1.bin +0 -0
  22. isodec/modelparams/phase_model_1.pth +0 -0
  23. isodec/modelparams/phase_model_4.bin +0 -0
  24. isodec/modelparams/phase_model_4.pth +0 -0
  25. isodec/modelparams/phase_model_8.bin +0 -0
  26. isodec/modelparams/phase_model_8.pth +0 -0
  27. isodec/modelparams/phase_model_b_1_0.bin +0 -0
  28. isodec/modelparams/phase_model_b_1_0.pth +0 -0
  29. isodec/modelparams/phase_model_b_4_0.bin +0 -0
  30. isodec/modelparams/phase_model_b_4_0.pth +0 -0
  31. isodec/modelparams/phase_model_b_4_1.bin +0 -0
  32. isodec/modelparams/phase_model_b_4_1.pth +0 -0
  33. isodec/modelparams/phase_model_b_8_0.bin +0 -0
  34. isodec/modelparams/phase_model_b_8_0.pth +0 -0
  35. isodec/modelparams/phase_model_b_8_1.bin +0 -0
  36. isodec/modelparams/phase_model_b_8_1.pth +0 -0
  37. isodec/modelparams/phase_model_c_1_0.bin +0 -0
  38. isodec/modelparams/phase_model_c_1_0.pth +0 -0
  39. isodec/modelparams/phase_model_c_4_0.bin +0 -0
  40. isodec/modelparams/phase_model_c_4_0.pth +0 -0
  41. isodec/modelparams/phase_model_c_4_1.bin +0 -0
  42. isodec/modelparams/phase_model_c_4_1.pth +0 -0
  43. isodec/modelparams/phase_model_c_8_0.bin +0 -0
  44. isodec/modelparams/phase_model_c_8_0.pth +0 -0
  45. isodec/modelparams/phase_model_c_8_1.bin +0 -0
  46. isodec/modelparams/phase_model_c_8_1.pth +0 -0
  47. isodec/modelparams/phase_model_p_1_0.bin +0 -0
  48. isodec/modelparams/phase_model_p_1_0.pth +0 -0
  49. isodec/modelparams/phase_model_p_4_0.bin +0 -0
  50. isodec/modelparams/phase_model_p_4_0.pth +0 -0
  51. isodec/modelparams/phase_model_p_4_1.bin +0 -0
  52. isodec/modelparams/phase_model_p_4_1.pth +0 -0
  53. isodec/modelparams/phase_model_p_8_0.bin +0 -0
  54. isodec/modelparams/phase_model_p_8_0.pth +0 -0
  55. isodec/modelparams/phase_model_p_8_1.bin +0 -0
  56. isodec/modelparams/phase_model_p_8_1.pth +0 -0
  57. isodec/models.py +553 -0
  58. isodec/msalign_export.py +211 -0
  59. isodec/plots.py +254 -0
  60. isodec/runtime.py +277 -0
  61. isodec/tools.py +112 -0
  62. isodec/train.py +37 -0
  63. isodec/train_profile.py +102 -0
  64. isodec/trainingdata.py +211 -0
  65. isodec/trainingdata_profile.py +192 -0
  66. isodec-2.0.0b2.dist-info/METADATA +121 -0
  67. isodec-2.0.0b2.dist-info/RECORD +74 -0
  68. isodec-2.0.0b2.dist-info/WHEEL +5 -0
  69. isodec-2.0.0b2.dist-info/entry_points.txt +3 -0
  70. isodec-2.0.0b2.dist-info/licenses/LICENSE +28 -0
  71. isodec-2.0.0b2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12 -0
  72. isodec-2.0.0b2.dist-info/licenses/extern/IsoGen/LICENSE +11 -0
  73. isodec-2.0.0b2.dist-info/licenses/extern/IsoGen/src/fftw/COPYING +340 -0
  74. isodec-2.0.0b2.dist-info/licenses/extern/IsoGen/src/fftw/COPYRIGHT +19 -0
isodec/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """IsoDec charge-state assignment and deconvolution."""
2
+
3
+ from ._version import __version__
4
+ from .config import IsoDecConfig
5
+ from .c_interface import IsoDecWrapper
6
+ from .runtime import IsoDecRuntime
7
+
8
+ __all__ = ["IsoDecConfig", "IsoDecRuntime", "IsoDecWrapper", "__version__"]
isodec/__main__.py ADDED
@@ -0,0 +1,45 @@
1
+ """Command-line interface for IsoDec."""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ if not __package__:
8
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
9
+ __package__ = "isodec"
10
+
11
+ from .runtime import IsoDecRuntime
12
+
13
+
14
+ def build_parser():
15
+ parser = argparse.ArgumentParser(
16
+ prog="isodec",
17
+ description="Assign charge states and deconvolve isotopically resolved mass spectra.",
18
+ )
19
+ parser.add_argument("spectrum", type=Path, help="Input spectrum (TXT, DAT, CSV, NPZ, or a UniDec-supported format)")
20
+ parser.add_argument("-o", "--output", type=Path, help="Output TSV filename")
21
+ parser.add_argument("--centroided", action="store_true", help="Treat the input as centroid data")
22
+ parser.add_argument("--type", choices=("PEPTIDE", "RNA"), default="PEPTIDE", help="Analyte isotope model")
23
+ parser.add_argument("--quiet", action="store_true", help="Suppress progress output")
24
+ return parser
25
+
26
+
27
+ def main(argv=None):
28
+ args = build_parser().parse_args(argv)
29
+ if not args.spectrum.is_file():
30
+ raise SystemExit(f"Spectrum does not exist: {args.spectrum}")
31
+
32
+ engine = IsoDecRuntime(verbose=not args.quiet)
33
+ engine.analyte_type = args.type
34
+ engine.process_file(
35
+ str(args.spectrum),
36
+ assume_centroided=args.centroided,
37
+ verbose=not args.quiet,
38
+ )
39
+ output = args.output or args.spectrum.with_name(f"{args.spectrum.stem}_isodec.tsv")
40
+ engine.pks.export_tsv(output, report_multiple_monoisos=engine.config.report_multiple_monoisos)
41
+ return 0
42
+
43
+
44
+ if __name__ == "__main__":
45
+ raise SystemExit(main())
isodec/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Single source of truth for the package version."""
2
+
3
+ __version__ = "2.0.0b2"
isodec/altdecon.py ADDED
@@ -0,0 +1,127 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ if not __package__:
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
8
+ __package__ = "isodec"
9
+
10
+ from . import tools as ud
11
+
12
+ example = np.array(
13
+ [
14
+ [5.66785531e02, 1.47770838e06],
15
+ [5.67057354e02, 1.54980838e06],
16
+ [5.67507468e02, 5.21600520e07],
17
+ [5.67708173e02, 8.35557760e07],
18
+ [5.67908401e02, 7.28264240e07],
19
+ [5.68060254e02, 1.87337225e06],
20
+ [5.68108674e02, 4.35435520e07],
21
+ [5.68239256e02, 3.88155375e06],
22
+ [5.68309390e02, 2.05468060e07],
23
+ [5.68509951e02, 7.18109250e06],
24
+ [5.68707871e02, 2.30373500e06],
25
+ [5.69150563e02, 1.57598062e06],
26
+ [5.69243121e02, 1.96390440e07],
27
+ [5.69334393e02, 6.82677120e07],
28
+ [5.69425337e02, 1.22867432e08],
29
+ [5.69516492e02, 1.45702336e08],
30
+ [5.69607541e02, 1.20801936e08],
31
+ [5.69698595e02, 1.06786072e08],
32
+ [5.69789906e02, 6.56232960e07],
33
+ [5.69881208e02, 3.41013880e07],
34
+ [5.69972168e02, 1.70930360e07],
35
+ [5.70063432e02, 9.17621100e06],
36
+ [5.70699369e02, 1.96462650e06],
37
+ ]
38
+ )
39
+
40
+ import numpy as np
41
+ from scipy import signal
42
+
43
+ MAX_CHARGE = 50
44
+
45
+
46
+ def gen_thrash_arrays(centroids, startpad=10):
47
+ max_mz = centroids[-1, 0]
48
+ min_mz = centroids[0, 0]
49
+
50
+ additional = 4 - (max_mz - min_mz)
51
+ if additional < 0:
52
+ additional = 0
53
+ else:
54
+ max_mz += additional / 2
55
+ min_mz -= additional / 2
56
+
57
+ num_l = int((max_mz - min_mz) * MAX_CHARGE * 8)
58
+
59
+ intx = np.linspace(min_mz, max_mz, num_l)
60
+ if len(intx) <= 1:
61
+ return None, None, None, None, None, None
62
+
63
+ linear_data = ud.lintegrate(centroids, intx, fastmode=True)
64
+ if len(linear_data) <= 1:
65
+ return None, None, None, None, None, None
66
+
67
+ corry = signal.fftconvolve(linear_data[:, 1], linear_data[:, 1][::-1], mode='same')
68
+ maxposition = np.argmax(corry)
69
+ ac = corry[maxposition + startpad:]
70
+
71
+
72
+ acx = intx - intx[0]
73
+ acx = 1 / acx[startpad:len(ac) + startpad]
74
+
75
+ fft = np.fft.fft(linear_data[:, 1])
76
+ fft = np.abs(fft)
77
+ fft = fft[startpad:len(ac) + startpad]
78
+
79
+ fftx = np.fft.fftfreq(len(linear_data[:, 1]), d=(linear_data[1, 0] - linear_data[0, 0]))
80
+ fftx = fftx[startpad:len(ac) + startpad]
81
+ b1 = fftx < MAX_CHARGE
82
+ fft = fft[b1]
83
+ fftx = fftx[b1]
84
+
85
+ fft /= np.max(fft)
86
+ ac2 = ud.lintegrate(np.transpose([acx, ac]), fftx)
87
+
88
+
89
+ acx, ac = ac2[:, 0], ac2[:, 1]
90
+ ac /= np.max(ac)
91
+ mul = ac * fft
92
+ return linear_data, ac, fft, mul, fftx, acx
93
+
94
+ def thrash_predict(centroids):
95
+ linear_data, ac, fft, mul, fftx, acx = gen_thrash_arrays(centroids)
96
+ if linear_data is None or len(mul) == 0:
97
+ return 0
98
+ maxpos = np.argmax(mul)
99
+
100
+ charge = acx[maxpos]
101
+ return round(charge)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ example = np.loadtxt("Z:\\Group Share\\JGP\\MockData\\mockdata_centroids.csv", delimiter=",")
106
+ example = ud.datachop(example, 326,327)
107
+ for i in range(len(example)):
108
+ plt.plot([example[i, 0], example[i, 0]], [0, example[i, 1]], color="black")
109
+ plt.show()
110
+
111
+
112
+
113
+ raw_data = example
114
+ charge_state = thrash_predict(raw_data)
115
+ print(charge_state)
116
+
117
+ linear_data, ac, fft, mul, fftx, acx = gen_thrash_arrays(raw_data)
118
+
119
+ plt.subplot(121)
120
+ plt.plot(linear_data[:, 0], linear_data[:, 1])
121
+ plt.subplot(122)
122
+ plt.plot(acx, mul, label="Mul")
123
+ plt.plot(acx, ac-1, label="AC")
124
+ plt.plot(fftx, fft-2, label="FFT")
125
+ plt.legend()
126
+ plt.show()
127
+
Binary file
isodec/bin/isogen.dll ADDED
Binary file
isodec/bin/libmmd.dll ADDED
Binary file
Binary file
@@ -0,0 +1,56 @@
1
+ """Convert a PyTorch model to a binary file and C header."""
2
+
3
+ from pathlib import Path
4
+
5
+ import torch
6
+
7
+
8
+ def pth_to_bin(pth_file, bin_file):
9
+ """Flatten a PyTorch state dictionary into a raw float32 binary file."""
10
+ pth_file = Path(pth_file)
11
+ bin_file = Path(bin_file)
12
+
13
+ state_dict = torch.load(pth_file, map_location="cpu", weights_only=True)
14
+ weights = torch.cat([tensor.flatten() for tensor in state_dict.values()])
15
+ weights.detach().cpu().numpy().tofile(bin_file)
16
+
17
+
18
+ def bin_to_header(bin_file, header_file, varname="model_weights"):
19
+ """Write the bytes from ``bin_file`` to a C array in ``header_file``."""
20
+ bin_file = Path(bin_file)
21
+ header_file = Path(header_file)
22
+ data = bin_file.read_bytes()
23
+ guard = f"{varname.upper()}_H"
24
+
25
+ with header_file.open("w", encoding="ascii", newline="\n") as outfile:
26
+ outfile.write(f"#ifndef {guard}\n")
27
+ outfile.write(f"#define {guard}\n\n")
28
+ outfile.write(f"// Generated from {bin_file.name}\n")
29
+ outfile.write(f"static const unsigned char {varname}[] = {{\n")
30
+
31
+ for start in range(0, len(data), 12):
32
+ chunk = data[start:start + 12]
33
+ values = ", ".join(f"0x{byte:02x}" for byte in chunk)
34
+ if start + 12 < len(data):
35
+ values += ","
36
+ outfile.write(f" {values}\n")
37
+
38
+ outfile.write("};\n")
39
+ outfile.write(f"static const unsigned int {varname}_len = {len(data)};\n\n")
40
+ outfile.write(f"#endif // {guard}\n")
41
+
42
+
43
+ if __name__ == "__main__":
44
+ isodec_dir = Path(__file__).resolve().parent
45
+
46
+ model_dir = isodec_dir / "modelparams"
47
+ input_pth = model_dir / "phase_model_8.pth"
48
+ output_bin = model_dir / "phase_model_8.bin"
49
+ output_header = isodec_dir / "src_cmake" / "phase_model_8.h"
50
+ variable_name = "phase_model_8_bin"
51
+
52
+ pth_to_bin(input_pth, output_bin)
53
+ print("Wrote binary model:", output_bin)
54
+
55
+ bin_to_header(output_bin, output_header, variable_name)
56
+ print("Wrote C header:", output_header)
isodec/c_interface.py ADDED
@@ -0,0 +1,434 @@
1
+ import ctypes
2
+ from importlib import metadata
3
+ import os
4
+ import platform
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+
10
+ if not __package__:
11
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
12
+ __package__ = "isodec"
13
+
14
+ from .match import MatchedPeak, MatchedCollection
15
+ from .config import IsoDecConfig
16
+
17
+ _system = platform.system()
18
+ _library_names = {
19
+ "Windows": "isodeclib.dll",
20
+ "Linux": "isodeclib.so",
21
+ "Darwin": "isodeclib.dylib",
22
+ }
23
+ try:
24
+ dllname = _library_names[_system]
25
+ except KeyError as error:
26
+ raise ImportError(f"IsoDec does not support {_system!r}") from error
27
+
28
+ _package_dir = Path(__file__).resolve().parent
29
+ _packaged_library_path = _package_dir / "bin" / dllname
30
+ _source_library_path = _package_dir / dllname
31
+
32
+
33
+ def _editable_library_path():
34
+ """Locate scikit-build's native install during an editable install."""
35
+ try:
36
+ candidate = Path(
37
+ metadata.distribution("isodec").locate_file(
38
+ Path("isodec") / "bin" / dllname
39
+ )
40
+ )
41
+ except metadata.PackageNotFoundError:
42
+ return None
43
+ return candidate if candidate.is_file() else None
44
+
45
+ if _packaged_library_path.is_file():
46
+ default_dll_path = _packaged_library_path
47
+ elif (_package_dir.parent / "pyproject.toml").is_file() and _source_library_path.is_file():
48
+ default_dll_path = _source_library_path
49
+ elif _editable_library_path() is not None:
50
+ default_dll_path = _editable_library_path()
51
+ else:
52
+ default_dll_path = _packaged_library_path
53
+
54
+ example = np.array(
55
+ [
56
+ [5.66785531e02, 1.47770838e06],
57
+ [5.67057354e02, 1.54980838e06],
58
+ [5.67507468e02, 5.21600520e07],
59
+ [5.67708173e02, 8.35557760e07],
60
+ [5.67908401e02, 7.28264240e07],
61
+ [5.68060254e02, 1.87337225e06],
62
+ [5.68108674e02, 4.35435520e07],
63
+ [5.68239256e02, 3.88155375e06],
64
+ [5.68309390e02, 2.05468060e07],
65
+ [5.68509951e02, 7.18109250e06],
66
+ [5.68707871e02, 2.30373500e06],
67
+ [5.69150563e02, 1.57598062e06],
68
+ [5.69243121e02, 1.96390440e07],
69
+ [5.69334393e02, 6.82677120e07],
70
+ [5.69425337e02, 1.22867432e08],
71
+ [5.69516492e02, 1.45702336e08],
72
+ [5.69607541e02, 1.20801936e08],
73
+ [5.69698595e02, 1.06786072e08],
74
+ [5.69789906e02, 6.56232960e07],
75
+ [5.69881208e02, 3.41013880e07],
76
+ [5.69972168e02, 1.70930360e07],
77
+ [5.70063432e02, 9.17621100e06],
78
+ [5.70699369e02, 1.96462650e06],
79
+ ]
80
+ )
81
+
82
+ isodist = ctypes.c_float * 64
83
+ matchedinds = ctypes.c_int * 32
84
+
85
+
86
+ # print(isodist)
87
+ class MPStruct(ctypes.Structure):
88
+ _fields_ = [
89
+ ("mz", ctypes.c_float),
90
+ ("z", ctypes.c_int),
91
+ ("monoiso", ctypes.c_float),
92
+ ("peakmass", ctypes.c_float),
93
+ ("avgmass", ctypes.c_float),
94
+ ("area", ctypes.c_float),
95
+ ("peakint", ctypes.c_float),
96
+ ("matchedindsiso", ctypes.c_int * 64),
97
+ ("matchedindsexp", ctypes.c_int * 64),
98
+ ("isomz", isodist),
99
+ ("isodist", isodist),
100
+ ("isomass", isodist),
101
+ ("monoisos", ctypes.c_float * 16),
102
+ ("startindex", ctypes.c_int),
103
+ ("endindex", ctypes.c_int),
104
+ ("score", ctypes.c_float),
105
+ ("realisolength", ctypes.c_int),
106
+ ]
107
+
108
+
109
+ class IDSettings(ctypes.Structure):
110
+ _fields_ = [
111
+ ("phaseres", ctypes.c_int),
112
+ ("verbose", ctypes.c_int),
113
+ ("peakwindow", ctypes.c_int),
114
+ ("peakthresh", ctypes.c_float),
115
+ ("minpeaks", ctypes.c_int),
116
+ ("css_thresh", ctypes.c_float),
117
+ ("matchtol", ctypes.c_float),
118
+ ("maxshift", ctypes.c_int),
119
+ ("mzwindow", ctypes.c_float * 2),
120
+ ("plusoneintwindow", ctypes.c_float * 2),
121
+ ("knockdown_rounds", ctypes.c_int),
122
+ ("min_score_diff", ctypes.c_float),
123
+ ("minareacovered", ctypes.c_float),
124
+ ("isolength", ctypes.c_int),
125
+ ("mass_diff_c", ctypes.c_double),
126
+ ("adductmass", ctypes.c_float),
127
+ ("minusoneaszero", ctypes.c_int),
128
+ ("isotopethreshold", ctypes.c_float),
129
+ ("datathreshold", ctypes.c_float),
130
+ ("zscore_threshold", ctypes.c_float),
131
+ ]
132
+
133
+
134
+ class IDConfig(ctypes.Structure):
135
+ _fields_ = [
136
+ ("verbose", ctypes.c_int),
137
+ ("pres", ctypes.c_int),
138
+ ("maxz", ctypes.c_int),
139
+ ("elen", ctypes.c_int),
140
+ ("l1", ctypes.c_int),
141
+ ("l2", ctypes.c_int),
142
+ ("l3", ctypes.c_int),
143
+ ("l4", ctypes.c_int),
144
+ ("dlen", ctypes.c_int),
145
+ ]
146
+
147
+
148
+ def config_to_settings(config):
149
+ # print(config)
150
+ settings = IDSettings()
151
+ settings.phaseres = int(config.phaseres)
152
+ settings.verbose = int(config.verbose)
153
+ settings.peakwindow = int(config.peakwindow)
154
+ settings.peakthresh = float(config.peakthresh)
155
+ settings.minpeaks = int(config.minpeaks)
156
+ settings.css_thresh = float(config.css_thresh)
157
+ settings.matchtol = float(config.matchtol)
158
+ settings.maxshift = int(config.maxshift)
159
+ settings.mzwindow = (config.mzwindowlb, config.mzwindowub)
160
+ settings.plusoneintwindow = (config.plusoneintwindowlb, config.plusoneintwindowub)
161
+ settings.knockdown_rounds = int(config.knockdown_rounds)
162
+ settings.min_score_diff = config.min_score_diff
163
+ settings.minareacovered = config.minareacovered
164
+ settings.isolength = 64
165
+ settings.mass_diff_c = config.mass_diff_c
166
+ settings.adductmass = config.adductmass
167
+ settings.minusoneaszero = config.minusoneaszero
168
+ settings.isotopethreshold = config.isotopethreshold
169
+ settings.datathreshold = config.datathreshold
170
+ settings.zscore_threshold = config.zscore_threshold
171
+ return settings
172
+
173
+
174
+ class IsoDecWrapper:
175
+ def __init__(self, dllpath=None):
176
+ if dllpath is None:
177
+ dllpath = default_dll_path
178
+
179
+ dllpath = Path(dllpath).resolve()
180
+ if not dllpath.is_file():
181
+ raise ImportError(
182
+ f"IsoDec's native library is missing: {dllpath}. Reinstall "
183
+ "isodec using a compatible wheel, or build from source "
184
+ "with CMake and a native compiler."
185
+ )
186
+
187
+ modelpath = _package_dir / "modelparams"
188
+
189
+ self.modeldir = modelpath
190
+
191
+ self._dll_directory_handle = None
192
+ if _system == "Windows":
193
+ self._dll_directory_handle = os.add_dll_directory(str(dllpath.parent))
194
+ try:
195
+ self.c_lib = ctypes.CDLL(str(dllpath))
196
+ except OSError as error:
197
+ raise ImportError(f"Unable to load IsoDec's native library {dllpath}: {error}") from error
198
+
199
+ self.c_lib.encode.argtypes = [
200
+ ctypes.POINTER(ctypes.c_double),
201
+ ctypes.POINTER(ctypes.c_float),
202
+ ctypes.c_int,
203
+ ctypes.POINTER(ctypes.c_float),
204
+ IDConfig,
205
+ IDSettings,
206
+ ]
207
+ self.c_lib.encode.restype = ctypes.c_int
208
+
209
+ self.c_lib.predict_charge.argtypes = [
210
+ ctypes.POINTER(ctypes.c_double),
211
+ ctypes.POINTER(ctypes.c_float),
212
+ ctypes.c_int,
213
+ ctypes.c_char_p, # Model path
214
+ ]
215
+ self.c_lib.predict_charge.restype = ctypes.c_int
216
+
217
+ self.c_lib.process_spectrum.argtypes = [
218
+ ctypes.POINTER(ctypes.c_double),
219
+ ctypes.POINTER(ctypes.c_float),
220
+ ctypes.c_int,
221
+ ctypes.c_char_p,
222
+ ctypes.POINTER(MPStruct),
223
+ IDSettings,
224
+ ctypes.c_char_p,
225
+ ]
226
+ self.c_lib.process_spectrum.restype = ctypes.c_int
227
+
228
+ self.c_lib.DefaultSettings.argtypes = []
229
+ self.c_lib.DefaultSettings.restype = IDSettings
230
+
231
+ self.modeldir = str(modelpath)
232
+ # self.modelpath = ctypes.c_char_p(
233
+ # os.path.join(self.modeldir, "phase_model_8.bin").encode()
234
+ # )
235
+ # Create null pointer for model path
236
+ self.modelpath = None
237
+ self.config = IsoDecConfig()
238
+ # self.determine_model()
239
+
240
+ def encode(self, centroids, maxz=50, phaseres=8, config=None):
241
+ cmz = centroids[:, 0].astype(np.double)
242
+ cint = centroids[:, 1].astype(np.float32)
243
+ elen = maxz * phaseres
244
+ emat = np.zeros(elen).astype(np.float32)
245
+ idconf = IDConfig()
246
+ idconf.pres = phaseres
247
+ idconf.maxz = maxz
248
+ idconf.elen = elen
249
+
250
+ if config is not None:
251
+ self.config = config
252
+ settings = config_to_settings(self.config)
253
+
254
+ self.c_lib.encode(
255
+ cmz.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
256
+ cint.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
257
+ ctypes.c_int(len(cmz)),
258
+ emat.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
259
+ idconf,
260
+ settings,
261
+ )
262
+ # Convert emat to numpy
263
+ emat = np.ctypeslib.as_array(emat)
264
+ return emat
265
+
266
+ def predict_charge(self, centroids, config=None):
267
+
268
+ cmz = centroids[:, 0].astype(np.double)
269
+ cint = centroids[:, 1].astype(np.float32)
270
+ # charge = ctypes.c_int(0)
271
+ # self.modelpath = ctypes.c_char_p(None)
272
+
273
+ charge = self.c_lib.predict_charge(
274
+ cmz.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
275
+ cint.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
276
+ ctypes.c_int(len(cmz)),
277
+ self.modelpath,
278
+ # ctypes.byref(charge),
279
+ )
280
+ return charge
281
+
282
+ def process_spectrum(self, centroids, pks=None, config=None, input_type=None):
283
+
284
+ if input_type is None:
285
+ input_type = "Peptide"
286
+ type_c = ctypes.c_char_p(input_type.encode('utf-8'))
287
+
288
+ cmz = centroids[:, 0].astype(np.double)
289
+ cint = centroids[:, 1].astype(np.float32)
290
+ n = len(cmz)
291
+
292
+ if config is not None:
293
+ self.config = config
294
+ settings = config_to_settings(self.config)
295
+
296
+ elems = (MPStruct * n)()
297
+ matchedpeaks = ctypes.cast(elems, ctypes.POINTER(MPStruct))
298
+
299
+ nmatched = self.c_lib.process_spectrum(
300
+ cmz.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
301
+ cint.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
302
+ ctypes.c_int(n),
303
+ self.modelpath,
304
+ matchedpeaks,
305
+ settings,
306
+ type_c,
307
+ )
308
+
309
+ if pks is None:
310
+ pks = MatchedCollection()
311
+ for p in matchedpeaks[:nmatched]:
312
+ if p.z == 0:
313
+ continue
314
+
315
+ # Extract peak centroid data
316
+ startindex = p.startindex
317
+ endindex = p.endindex
318
+ if startindex < 0 or endindex < 0 or endindex <= startindex:
319
+ raise ValueError("Invalid start or end index in matched peak.")
320
+ pkcent = centroids[startindex:endindex + 1]
321
+
322
+ pk = MatchedPeak(p.z, p.mz, p.avgmass, centroids=pkcent, config=self.config)
323
+ pk.monoiso = p.monoiso
324
+ pk.peakmass = p.peakmass
325
+ pk.avgmass = p.avgmass
326
+
327
+ monoisos = np.array(p.monoisos)
328
+ monoisos = monoisos[monoisos > 0]
329
+ pk.monoisos = monoisos
330
+
331
+ if config is not None:
332
+ pk.scan = config.activescan
333
+ pk.ms_order = config.activescanorder
334
+ pk.rt = config.activescanrt
335
+
336
+ isodist = np.array(p.isodist)
337
+ isomz = np.array(p.isomz)
338
+ isomass = np.array(p.isomass)
339
+ b1 = isodist > np.amax(isodist) * 0.001
340
+ isodist = isodist[b1]
341
+ isomz = isomz[b1]
342
+ isomass = isomass[b1]
343
+ pk.matchedintensity = np.sum(isodist)
344
+ pk.peakint = p.peakint
345
+
346
+ # pk.isodist = calc_isotope_dist(p.monoiso, p.z)
347
+ pk.isodist = np.transpose((isomz, isodist))
348
+ # pk.isodist[:, 1] = p.peakint
349
+
350
+ pk.massdist = np.transpose((isomass, isodist))
351
+
352
+ pk.startindex = p.startindex
353
+ pk.endindex = p.endindex
354
+
355
+ pks.add_peak(pk)
356
+ pks.add_pk_to_masses(pk, config=self.config)
357
+ return pks
358
+
359
+ def determine_model(self, default=True):
360
+ if default:
361
+ self.modelpath = None
362
+ else:
363
+ print("Model Directory:", self.modeldir)
364
+ if self.config.phaseres == 4:
365
+ self.modelpath = ctypes.c_char_p(
366
+ os.path.join(self.modeldir, "phase_model_4.bin").encode('utf-8')
367
+ )
368
+ elif self.config.phaseres == 8:
369
+
370
+ self.modelpath = ctypes.c_char_p(
371
+ os.path.join(self.modeldir, "phase_model_8.bin").encode('utf-8')
372
+ )
373
+ else:
374
+ print("Invalid phase resolution.", self.config.phaseres)
375
+ raise ValueError("Invalid phase resolution.")
376
+
377
+ if self.config.verbose:
378
+ print(
379
+ "Running C code with phaseres of:",
380
+ self.config.phaseres,
381
+ )
382
+
383
+
384
+ if __name__ == "__main__":
385
+ eng = IsoDecWrapper()
386
+ #
387
+ # eng.config.phaseres = 4
388
+ # eng.encode(example)
389
+ # print(eng.predict_charge(example))
390
+ # dat = eng.process_spectrum(example)
391
+ #
392
+ # print(dat)
393
+ filepath = "C:\\Data\\IsoNN\\test2.txt"
394
+ spectrum = np.loadtxt(filepath, skiprows=0)
395
+ pks = eng.process_spectrum(spectrum)
396
+ print(len(pks.peaks))
397
+
398
+ # Find peak with monoiso near 6396
399
+ for pk in pks.masses:
400
+ if 6395 < pk.monoiso < 6397:
401
+ print("Found Peak:", pk)
402
+ cplot(pk.decon_centroids, color="k", factor=1)
403
+ cplot(pk.massdist, color="k", factor=-1)
404
+ import matplotlib.pyplot as plt
405
+
406
+ colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
407
+ for i, p in enumerate(pk.clusters):
408
+ print(" Subpeak:", p)
409
+ # if p.monoiso > 6396:
410
+ # continue
411
+ cplot(p.decon_centroids, color=colors[i % len(colors)], factor=1)
412
+ cplot(p.massdist, color=colors[i % len(colors)], factor=-1)
413
+ plt.show()
414
+
415
+
416
+ exit()
417
+ # filepath = "C:\\Data\\IsoNN\\test2.txt"
418
+ filepath = "Z:\\Group Share\\JGP\\js8b05641_si_001\\etd_spectrum.txt"
419
+ spectrum = np.loadtxt(filepath, skiprows=0)
420
+ spectrum2 = datachop(spectrum, 891.195, 893.757)
421
+
422
+
423
+ e1 = eng.encode(spectrum2)
424
+ print("Encoded:", e1.shape)
425
+
426
+ # z = eng.predict_charge(spectrum2)
427
+ # print("Predicted Charge:", z)
428
+
429
+ # exit()
430
+ pks = eng.process_spectrum(spectrum2)
431
+ print(len(pks.peaks))
432
+ # exit()
433
+ plot_pks(pks, centroids=spectrum, show=True, title="C Interface")
434
+ plt.show()