From 04e0ca6c712bff3ee2d544684b6a3f6e1f6650f3 Mon Sep 17 00:00:00 2001 From: yangkl96 Date: Thu, 16 Jan 2025 15:36:55 -0500 Subject: [PATCH] predfull --- clients/python/test/PredFull/__init__.py | 0 .../PredFull/numpy_arrays/AA1intsCID20.npy | Bin 0 -> 232 bytes .../PredFull/numpy_arrays/AA1mzsCID20.npy | Bin 0 -> 232 bytes .../HIISVMoxR2CID30_intensities.npy | Bin 0 -> 716 bytes .../numpy_arrays/HIISVMoxR2CID30_mzs.npy | Bin 0 -> 716 bytes .../HIISVMoxR2CID30_rawspectrum.npy | Bin 0 -> 80128 bytes ...EPTIPEPTIPEPTIPEPTIPEPTIPEPT2intsHCD21.npy | Bin 0 -> 664 bytes ...PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2mzsHCD21.npy | Bin 0 -> 664 bytes .../RHKDESTNQCGPAVILM(ox)FYW4intsHCD23.npy | Bin 0 -> 2552 bytes .../RHKDESTNQCGPAVILM(ox)FYW4mzsHCD23.npy | Bin 0 -> 2552 bytes .../RHKDESTNQCGPAVILMFYW3intsCID22.npy | Bin 0 -> 176 bytes .../RHKDESTNQCGPAVILMFYW3mzsCID22.npy | Bin 0 -> 176 bytes clients/python/test/PredFull/test_PredFull.py | 117 +++++ .../PredFull/test_PredFull_Postprocess.py | 68 +++ .../test_PredFull_Preprocess_charge.py | 73 +++ ...st_PredFull_Preprocess_collision_energy.py | 39 ++ .../test_PredFull_Preprocess_combine_meta.py | 123 +++++ ...PredFull_Preprocess_fragmentation_types.py | 111 +++++ .../PredFull/test_PredFull_Preprocess_mass.py | 53 +++ .../test_PredFull_Preprocess_peptide.py | 325 +++++++++++++ .../test/PredFull/test_PredFull_core.py | 439 ++++++++++++++++++ models/PredFull/PredFull/config.pbtxt | 165 +++++++ .../PredFull/PredFull_Postprocess/1/model.py | 75 +++ .../PredFull_Postprocess/config.pbtxt | 26 ++ .../PredFull_Preprocess_charge/1/model.py | 48 ++ .../PredFull_Preprocess_charge/config.pbtxt | 15 + .../1/model.py | 33 ++ .../config.pbtxt | 15 + .../1/model.py | 42 ++ .../config.pbtxt | 30 ++ .../1/model.py | 50 ++ .../config.pbtxt | 15 + .../PredFull_Preprocess_mass/1/model.py | 81 ++++ .../PredFull_Preprocess_mass/config.pbtxt | 22 + .../PredFull_Preprocess_peptide/1/model.py | 121 +++++ .../PredFull_Preprocess_peptide/config.pbtxt | 15 + models/PredFull/PredFull_core/1/.zenodo | 2 + models/PredFull/PredFull_core/config.pbtxt | 1 + 38 files changed, 2104 insertions(+) create mode 100644 clients/python/test/PredFull/__init__.py create mode 100644 clients/python/test/PredFull/numpy_arrays/AA1intsCID20.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/AA1mzsCID20.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_intensities.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_mzs.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_rawspectrum.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2intsHCD21.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2mzsHCD21.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILM(ox)FYW4intsHCD23.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILM(ox)FYW4mzsHCD23.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILMFYW3intsCID22.npy create mode 100644 clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILMFYW3mzsCID22.npy create mode 100644 clients/python/test/PredFull/test_PredFull.py create mode 100644 clients/python/test/PredFull/test_PredFull_Postprocess.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_charge.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_collision_energy.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_combine_meta.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_fragmentation_types.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_mass.py create mode 100644 clients/python/test/PredFull/test_PredFull_Preprocess_peptide.py create mode 100644 clients/python/test/PredFull/test_PredFull_core.py create mode 100644 models/PredFull/PredFull/config.pbtxt create mode 100644 models/PredFull/PredFull_Postprocess/1/model.py create mode 100644 models/PredFull/PredFull_Postprocess/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_charge/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_charge/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_collision_energy/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_collision_energy/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_combine_meta/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_combine_meta/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_fragmentation_types/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_fragmentation_types/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_mass/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_mass/config.pbtxt create mode 100644 models/PredFull/PredFull_Preprocess_peptide/1/model.py create mode 100644 models/PredFull/PredFull_Preprocess_peptide/config.pbtxt create mode 100644 models/PredFull/PredFull_core/1/.zenodo create mode 100644 models/PredFull/PredFull_core/config.pbtxt diff --git a/clients/python/test/PredFull/__init__.py b/clients/python/test/PredFull/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/clients/python/test/PredFull/numpy_arrays/AA1intsCID20.npy b/clients/python/test/PredFull/numpy_arrays/AA1intsCID20.npy new file mode 100644 index 0000000000000000000000000000000000000000..2cddabe93401dd6f7567210499de4a2382105f16 GIT binary patch literal 232 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$7W;&WW3bhL41Fp-?4z@YGTGk4OR$2%52HRAB_O{WCQMKiJ=xC$i{o8JF z*<2fzA`x4a9~Z4|8_3z32Nl|$6i~HhU}&&UDLrVj_nm~@tH4<{G9nhXlI4@FxJxHn Z2|7KnPFTLiYC=Pi&BFJSY@{vX>;O!KLK^@8 literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/AA1mzsCID20.npy b/clients/python/test/PredFull/numpy_arrays/AA1mzsCID20.npy new file mode 100644 index 0000000000000000000000000000000000000000..91282ae65f508e463ccbab0a428e0a607411b803 GIT binary patch literal 232 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$7W;&WW3bhL411@9ZGfu|F7l8N<5I+FoM?m}xh+hHm2O$3EG)t1vnSp`H td6pyyGXvSIK$-)Hd4ZT8hy{UI*f}jt7)Xl(u_RE9G>`^iSs<2o1^^zMIIREx literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_intensities.npy b/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_intensities.npy new file mode 100644 index 0000000000000000000000000000000000000000..a676e4ee43473354aaf920efc545e3e8b6677c3b GIT binary patch literal 716 zcmbV=`!CgT9EXpD7G37DE@UUBrts zI*G-GcIc?h=60GXN&LbOwuES7lKzFBU!FZrxsU$^-(U^FEkTATmQRV1iClytw>U?U ztxyz~B)ch#x*m}vi{<})kEnzce(R@5qmucpwmjzOWNT$B%oP4NJH6G;;`up4?i(*8 zmQ{@)eS0p{Y8#Ue>>{UdHHM1Apx;o&!kyX?eqsk{Wi#dVd&YANgT5KX_lbrR-@L}{Cf|GOMxHljB>mn(p)tm(>Hlgk- zr1)%8+H>$J(lbn`()ukkU6mh**iFVv1YsDQc;&K2UBE%Ns7^wXikHrVp3R2vEow* z6{n=d`v%PInG8P4mw4Rihv~=yN*&TAm$Sv3NrD!col;Q02~T`cFYa2nk}C~hu=f$l zCMEFJAEt@w5ZXs?$V6g6(d}JW&gdpj{{{*Wnsc7fm+13=oZWVBq*nn)=rCp5eqxmGZF2s784Yic$vVbfF7ba0bXZ#82DR02lcouD~ hEAUW_;d!nS3#wk4vz$iF0e7lv`Gxsr3wk&cioZ666rcbA literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_mzs.npy b/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_mzs.npy new file mode 100644 index 0000000000000000000000000000000000000000..ccb1e004e9620ef2aea2508cabc95be7ee74aea1 GIT binary patch literal 716 zcmbWw&r6eW9Ki8Sh-|G8>hiSEhsZq*9)I;+jHO-nd{I0o#lsE{lrr=gg1U`8BN(eg zhF%mQo-TWclnzFR6p4xvcrd|>9%`2sB6#WQP7xB~`}r3fym)^;pDzz{N7Ew@tzh73 zprTFM&O}kWuWAF?xTdRGHeW0iGr946anknhgP9q}=I%^o3O4(lzIZ~9>FRU!+`o_z z2Pq*A(M)+t(D0O2gb=|^+(r~JbfE{mxQm1#M3ScH5FN%S9>L_6rWPLK3C6gMQybHm zMGkoscq`E|D$KrT#(ww(`<$`A+QeV{!wLR#KQ)9K zkire3MT%<|q%beiOHv4!aNwfu@0Z!(q7F064j1MX>bok%C)DA#F{2Liy7bD5^s1<# zaf3!^yY!R}=~-x~^cMf$Nufk(jBn8Jt8?q7z3i|AGpu_^GQ;ZU?STxereVo5d5`ni literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_rawspectrum.npy b/clients/python/test/PredFull/numpy_arrays/HIISVMoxR2CID30_rawspectrum.npy new file mode 100644 index 0000000000000000000000000000000000000000..242e23311e0ee738925012fea25028d23e21da5d GIT binary patch literal 80128 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

)9}kc}t~CJkSe6GJrt~fn;-Es9Xbh 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 literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2mzsHCD21.npy b/clients/python/test/PredFull/numpy_arrays/PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2mzsHCD21.npy new file mode 100644 index 0000000000000000000000000000000000000000..8bf97cd779ba209194350a7de72e18b51c5918f6 GIT binary patch literal 664 zcmbWx&nrYx7{>8~qGUG4bUq!YPtQabUkn8f_ze>lbRgTeuc66W%3IiC#D8?|3 zNlYWAENhWmQ`Y$gxrJ?2sM;m>$bB-aoE(e;ICO`quye?KL=WQ_C(ND;T;dAX%09g& zZ*hlvJn;6B^c3LYiSC*7yy6Y-_)xb0lW)NPg>U8`@|XNomRq7NU#YgnkcF=dt}~ya&IMrWH;G^Ugi*Y7=2pE zuoe;IA#C;;EkcNRIZGlxr$rHRp53#c#U_dgZb>a}AeVR#nda_U<_+?=K@nnwJ0i$u S=uymQ`|^zcbEOMMPx=pF@!yF6 literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILM(ox)FYW4intsHCD23.npy b/clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILM(ox)FYW4intsHCD23.npy new file mode 100644 index 0000000000000000000000000000000000000000..6bcfd51560e6488e89f38fd994b35f4e4a902292 GIT binary patch literal 2552 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 literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILMFYW3intsCID22.npy b/clients/python/test/PredFull/numpy_arrays/RHKDESTNQCGPAVILMFYW3intsCID22.npy new file mode 100644 index 0000000000000000000000000000000000000000..0d64d27a18f55647dbb68162e6b5297c64cad7c0 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-Mmm~03bhL41FoR3^H%p14_Y<+yk*6+{HWEEow$(yYi7#i$L7cH@l Ue{Er#aJ0ud@E?nH`R@#C0F`AhqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-Mmm~03bhL41Fp0*VP|7wBj;I?#m)>3B`&jOmbfq^l({f00OA8c`~Zj< PDqM_%dtB1YcDn!o@6jtL literal 0 HcmV?d00001 diff --git a/clients/python/test/PredFull/test_PredFull.py b/clients/python/test/PredFull/test_PredFull.py new file mode 100644 index 00000000..c75a9a3f --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull.py @@ -0,0 +1,117 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +import tritonclient.grpc as grpcclient +import numpy as np +from pathlib import Path +import requests +import time + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + req = requests.get(f"{SERVER_HTTP}/v2/models/{MODEL_NAME}", timeout=1) + assert req.status_code == 200 + + +def test_available_grpc(): + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + assert triton_client.is_model_ready(MODEL_NAME) + + +def test_inference(): + SEQUENCES = np.array( + [ + ["AA"], + ["PEPTIPEPTIPEPTIPEPTIPEPTIPEPT"], + ["RHKDESTNQCGPAVILMFYW"], + ["RHKDESTNQCGPAVILM[UNIMOD:35]FYW"], + ], + dtype=np.object_, + ) + SEQUENCES_copy = SEQUENCES + for i in range(249): + for seq in SEQUENCES_copy: + SEQUENCES = np.append(SEQUENCES, [seq], axis=0) + len_s = len(SEQUENCES) + + charge = np.array([[i % 6 + 1] for i in range(len_s)], dtype=np.int32) + nce = np.array([[(19 + i) % 40 + 1] for i in range(len_s)], dtype=np.float32) + fragmentation_type = np.array( + [["HCD" if i % 2 != 0 else "CID"] for i in range(len_s)], dtype=np.object_ + ) + + start = time.time() + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + in_pep_seq = grpcclient.InferInput("peptide_sequences", [len_s, 1], "BYTES") + in_pep_seq.set_data_from_numpy(SEQUENCES) + + in_charge = grpcclient.InferInput("precursor_charges", [len_s, 1], "INT32") + in_charge.set_data_from_numpy(charge) + + in_nce = grpcclient.InferInput("collision_energies", [len_s, 1], "FP32") + in_nce.set_data_from_numpy(nce) + + in_frag = grpcclient.InferInput("fragmentation_types", [len_s, 1], "BYTES") + in_frag.set_data_from_numpy(fragmentation_type) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_pep_seq, in_charge, in_nce, in_frag], + outputs=[ + grpcclient.InferRequestedOutput("mzs"), + grpcclient.InferRequestedOutput("intensities"), + ], + ) + + fragmentmz = result.as_numpy("mzs") + intensities = result.as_numpy("intensities") + end = time.time() + print(fragmentmz[0:5, :]) + print(intensities[0:5, :]) + print(end - start) + + for i, (test_mzs, test_ints) in enumerate( + zip( + [ + "AA1mzsCID20.npy", + "PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2mzsHCD21.npy", + "RHKDESTNQCGPAVILMFYW3mzsCID22.npy", + "RHKDESTNQCGPAVILM(ox)FYW4mzsHCD23.npy", + ], + [ + "AA1intsCID20.npy", + "PEPTIPEPTIPEPTIPEPTIPEPTIPEPT2intsHCD21.npy", + "RHKDESTNQCGPAVILMFYW3intsCID22.npy", + "RHKDESTNQCGPAVILM(ox)FYW4intsHCD23.npy", + ], + ) + ): + # get predicted arrays + pred_mzs = fragmentmz[i, :] + pred_ints = intensities[i, :] + pred_mzs = pred_mzs[pred_mzs != -1] + pred_ints = pred_ints[pred_ints != -1] + pred_ints /= 1000 + + # get elements in test loaded array + ground_truth_mzs = np.load("test/PredFull/numpy_arrays/" + test_mzs) + p_indices = np.where(np.isin(pred_mzs, ground_truth_mzs))[0] + gt_indices = np.where(np.isin(ground_truth_mzs, pred_mzs))[0] + + print( + np.max( + abs( + pred_ints[p_indices] + - np.load("test/PredFull/numpy_arrays/" + test_ints)[gt_indices] + ) + ) + ) + assert np.allclose( + pred_ints[p_indices], + np.load("test/PredFull/numpy_arrays/" + test_ints)[gt_indices], + rtol=0, + atol=1e-1, + equal_nan=True, + ) diff --git a/clients/python/test/PredFull/test_PredFull_Postprocess.py b/clients/python/test/PredFull/test_PredFull_Postprocess.py new file mode 100644 index 00000000..811619a4 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Postprocess.py @@ -0,0 +1,68 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + spectrum = np.load("test/PredFull/numpy_arrays/HIISVMoxR2CID30_rawspectrum.npy") + in_spectrum = grpcclient.InferInput("spectrum", spectrum.shape, "FP32") + in_spectrum.set_data_from_numpy(spectrum) + + mass = np.array([[871.4]], dtype=np.float32) + in_mass = grpcclient.InferInput("precursor_mass_with_oxM", [1, 1], "FP32") + in_mass.set_data_from_numpy(mass) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_spectrum, in_mass], + outputs=[ + grpcclient.InferRequestedOutput("mzs"), + grpcclient.InferRequestedOutput("intensities"), + ], + ) + + mzs = result.as_numpy("mzs") + intensities = result.as_numpy("intensities") + print(mzs) + print(intensities) + print(mzs.shape) + + assert intensities.shape == mzs.shape + + ground_truth_intensities = np.load( + "test/PredFull/numpy_arrays/HIISVMoxR2CID30_intensities.npy" + ) + ground_truth_mzs = np.load("test/PredFull/numpy_arrays/HIISVMoxR2CID30_mzs.npy") + + ground_truth_mzs = ground_truth_mzs[ground_truth_intensities > 1] + ground_truth_intensities = ground_truth_intensities[ground_truth_intensities > 1] + + assert np.allclose( + intensities, + ground_truth_intensities, + rtol=0, + atol=1e-4, + ) + + assert np.allclose( + mzs, + ground_truth_mzs, + rtol=0, + atol=1e-4, + ) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_charge.py b/clients/python/test/PredFull/test_PredFull_Preprocess_charge.py new file mode 100644 index 00000000..7482d0c2 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_charge.py @@ -0,0 +1,73 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + charge = np.array([[3], [1]], dtype=np.int32) + in_charge = grpcclient.InferInput("precursor_charges", [len(charge), 1], "INT32") + in_charge.set_data_from_numpy(charge) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_charge], + outputs=[ + grpcclient.InferRequestedOutput("precursor_charges_in:0"), + ], + ) + + one_hot_charge = result.as_numpy("precursor_charges_in:0") + + ground_truth = np.array( + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + + print(one_hot_charge[1]) + assert np.array_equal(one_hot_charge[0], ground_truth) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_collision_energy.py b/clients/python/test/PredFull/test_PredFull_Preprocess_collision_energy.py new file mode 100644 index 00000000..7680da63 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_collision_energy.py @@ -0,0 +1,39 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + ces = np.array([[30]], dtype=np.float32) + in_ces = grpcclient.InferInput("collision_energies", [1, 1], "FP32") + in_ces.set_data_from_numpy(ces) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_ces], + outputs=[ + grpcclient.InferRequestedOutput("norm_collision_energy"), + ], + ) + + normce = result.as_numpy("norm_collision_energy") + + ground_truth = np.array([0.30]) + + assert np.allclose(normce[0], ground_truth, atol=1e-4) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_combine_meta.py b/clients/python/test/PredFull/test_PredFull_Preprocess_combine_meta.py new file mode 100644 index 00000000..765bc7a8 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_combine_meta.py @@ -0,0 +1,123 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + charge = np.array( + [ + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ], + dtype=np.float32, + ) + + fragmentation = np.array( + [ + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ], + dtype=np.float32, + ) + + mass = np.array([[0.042774]], dtype=np.float32) + nce = np.array([[0.30]], dtype=np.float32) + + in_charge = grpcclient.InferInput("precursor_charges_in:0", [1, 30], "FP32") + in_charge.set_data_from_numpy(charge) + + in_frag = grpcclient.InferInput("fragmentation_types_encoding", [1, 30], "FP32") + in_frag.set_data_from_numpy(fragmentation) + + in_mass = grpcclient.InferInput("precursor_mass", [1, 1], "FP32") + in_mass.set_data_from_numpy(mass) + + in_nce = grpcclient.InferInput("norm_collision_energy", [1, 1], "FP32") + in_nce.set_data_from_numpy(nce) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_charge, in_frag, in_mass, in_nce], + outputs=[ + grpcclient.InferRequestedOutput("meta_input"), + ], + ) + + meta = result.as_numpy("meta_input") + print(meta.shape) + assert meta.shape == (1, 3, 30) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_fragmentation_types.py b/clients/python/test/PredFull/test_PredFull_Preprocess_fragmentation_types.py new file mode 100644 index 00000000..6fabc904 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_fragmentation_types.py @@ -0,0 +1,111 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + fragmentation = np.array([["HCD"], ["CID"]], dtype=np.object_) + in_fragmentation = grpcclient.InferInput( + "fragmentation_types", fragmentation.shape, "BYTES" + ) + in_fragmentation.set_data_from_numpy(fragmentation) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_fragmentation], + outputs=[ + grpcclient.InferRequestedOutput("fragmentation_types_encoding"), + ], + ) + + one_fragmentation = result.as_numpy("fragmentation_types_encoding") + + ground_truth = np.array( + [ + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + ] + ) + + print(one_fragmentation) + print(ground_truth) + + assert np.array_equal(one_fragmentation, ground_truth) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_mass.py b/clients/python/test/PredFull/test_PredFull_Preprocess_mass.py new file mode 100644 index 00000000..68367f32 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_mass.py @@ -0,0 +1,53 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + + SEQUENCES = np.array( + [["HIISVM[UNIMOD:35]R"]], + dtype=np.object_, + ) + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + in_pep_seq = grpcclient.InferInput("peptide_sequences", [1, 1], "BYTES") + in_pep_seq.set_data_from_numpy(SEQUENCES) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_pep_seq], + outputs=[ + grpcclient.InferRequestedOutput("precursor_mass"), + ], + ) + + mass = result.as_numpy("precursor_mass") + ground_truth = 0.0427743459608515 + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_pep_seq], + outputs=[ + grpcclient.InferRequestedOutput("precursor_mass_with_oxM"), + ], + ) + + mass = result.as_numpy("precursor_mass_with_oxM") + ground_truth = 871.481819 + print(mass) + assert np.allclose(mass[0], ground_truth, atol=1e-4) diff --git a/clients/python/test/PredFull/test_PredFull_Preprocess_peptide.py b/clients/python/test/PredFull/test_PredFull_Preprocess_peptide.py new file mode 100644 index 00000000..4f396fb7 --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_Preprocess_peptide.py @@ -0,0 +1,325 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + + ground_truth = np.array( + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.68529457, + 0.0, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5654203, + 0.001, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5654203, + 0.002, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.43516013, + 0.003, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.49534208, + 0.004, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.6552024, + 0.005, + 0.0, + 1.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.78050554, + 0.006, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + ] + ) + + SEQUENCES = np.array( + [["HIISVM[UNIMOD:35]R"], ["HIISVMR"]], + dtype=np.object_, + ) + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + in_pep_seq = grpcclient.InferInput("peptide_sequences", [2, 1], "BYTES") + in_pep_seq.set_data_from_numpy(SEQUENCES) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_pep_seq], + outputs=[ + grpcclient.InferRequestedOutput("input"), + ], + ) + + peptide_embedding = result.as_numpy("input") + + assert np.allclose(peptide_embedding[0, 0:9, :], ground_truth, atol=1e-4) diff --git a/clients/python/test/PredFull/test_PredFull_core.py b/clients/python/test/PredFull/test_PredFull_core.py new file mode 100644 index 00000000..bdae68fb --- /dev/null +++ b/clients/python/test/PredFull/test_PredFull_core.py @@ -0,0 +1,439 @@ +from test.server_config import SERVER_GRPC, SERVER_HTTP +from pathlib import Path +from test.lib import lib_test_available_grpc, lib_test_available_http +import numpy as np +import tritonclient.grpc as grpcclient + + +# To ensure MODEL_NAME == test_.py +MODEL_NAME = Path(__file__).stem.replace("test_", "") + + +def test_available_http(): + lib_test_available_http(MODEL_NAME, SERVER_HTTP) + + +def test_available_grpc(): + lib_test_available_grpc(MODEL_NAME, SERVER_GRPC) + + +def test_inference(): + peptide_embedding = np.array( + [ + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.68529457, + 0.0, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5654203, + 0.001, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5654203, + 0.002, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.43516013, + 0.003, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.49534208, + 0.004, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.6552024, + 0.005, + 0.0, + 1.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.78050554, + 0.006, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + ] + ], + dtype=np.float32, + ) + meta = np.array( + [ + [ + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.04277435, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.3, + ], + ] + ], + dtype=np.float32, + ) + + triton_client = grpcclient.InferenceServerClient(url=SERVER_GRPC) + + in_peptide_embedding = grpcclient.InferInput( + "input", peptide_embedding.shape, "FP32" + ) + in_peptide_embedding.set_data_from_numpy(peptide_embedding) + + in_meta = grpcclient.InferInput("meta_input", meta.shape, "FP32") + in_meta.set_data_from_numpy(meta) + + result = triton_client.infer( + MODEL_NAME, + inputs=[in_peptide_embedding, in_meta], + outputs=[ + grpcclient.InferRequestedOutput("spectrum"), + ], + ) + + intensities = result.as_numpy("spectrum") + print(intensities) + + assert intensities.shape == (1, 20000) + + assert np.allclose( + intensities, + np.load("test/PredFull/numpy_arrays/HIISVMoxR2CID30_rawspectrum.npy"), + rtol=0, + atol=1e-4, + ) diff --git a/models/PredFull/PredFull/config.pbtxt b/models/PredFull/PredFull/config.pbtxt new file mode 100644 index 00000000..210ce5fb --- /dev/null +++ b/models/PredFull/PredFull/config.pbtxt @@ -0,0 +1,165 @@ +max_batch_size: 1000 +platform: "ensemble" +input [ + { + name: "peptide_sequences", + data_type: TYPE_STRING, + dims: [-1] + }, + { + name: "precursor_charges", + data_type: TYPE_INT32, + dims: [1] + }, + { + name: "collision_energies", + data_type: TYPE_FP32, + dims: [1] + }, + { + name: "fragmentation_types", + data_type: TYPE_STRING, + dims: [1] + } +] +output [ + { + name: "mzs", + data_type: TYPE_FP32, + dims: [-1] + }, + { + name: "intensities", + data_type: TYPE_FP32, + dims: [-1] + } +] + +ensemble_scheduling { + step [ + { + model_name: "PredFull_Preprocess_charge" + model_version: 1 + input_map { + key: "precursor_charges" + value: "precursor_charges" + }, + output_map { + key: "precursor_charges_in:0" + value: "precursor_charges_in:0" + } + }, + { + model_name: "PredFull_Preprocess_peptide" + model_version: 1 + input_map { + key: "peptide_sequences" + value: "peptide_sequences" + }, + output_map { + key: "input" + value: "input" + } + }, + { + model_name: "PredFull_Preprocess_collision_energy" + model_version: 1 + input_map { + key: "collision_energies" + value: "collision_energies" + }, + output_map { + key: "norm_collision_energy" + value: "norm_collision_energy" + } + }, + { + model_name: "PredFull_Preprocess_fragmentation_types" + model_version: 1 + input_map { + key: "fragmentation_types" + value: "fragmentation_types" + }, + output_map { + key: "fragmentation_types_encoding" + value: "fragmentation_types_encoding" + } + }, + { + model_name: "PredFull_Preprocess_mass" + model_version: 1 + input_map { + key: "peptide_sequences" + value: "peptide_sequences" + }, + output_map { + key: "precursor_mass" + value: "precursor_mass" + }, + output_map { + key: "precursor_mass_with_oxM" + value: "precursor_mass_with_oxM" + } + }, + { + model_name: "PredFull_Preprocess_combine_meta" + model_version: 1 + input_map { + key: "precursor_charges_in:0" + value: "precursor_charges_in:0" + }, + input_map { + key: "fragmentation_types_encoding" + value: "fragmentation_types_encoding" + }, + input_map { + key: "precursor_mass" + value: "precursor_mass" + }, + input_map { + key: "norm_collision_energy" + value: "norm_collision_energy" + } + output_map { + key: "meta_input" + value: "meta_input" + } + }, + { + model_name: "PredFull_core" + model_version: 1 + input_map { + key: "input" + value: "input" + }, + input_map { + key: "meta_input" + value: "meta_input" + }, + output_map { + key: "spectrum" + value: "spectrum" + } + }, + { + model_name: "PredFull_Postprocess" + model_version: 1 + input_map { + key: "spectrum" + value: "spectrum" + }, + input_map { + key: "precursor_mass_with_oxM" + value: "precursor_mass_with_oxM" + }, + output_map { + key: "mzs" + value: "mzs" + }, + output_map { + key: "intensities" + value: "intensities" + } + } + ] +} diff --git a/models/PredFull/PredFull_Postprocess/1/model.py b/models/PredFull/PredFull_Postprocess/1/model.py new file mode 100644 index 00000000..b94c65d0 --- /dev/null +++ b/models/PredFull/PredFull_Postprocess/1/model.py @@ -0,0 +1,75 @@ +import json +import numpy as np +import triton_python_backend_utils as pb_utils +import math + + +def sparse( + x, y, th=0.001 +): # original python version had th = 0.0002. That feel s abit unnecessary + x = np.asarray(x, dtype="float32") + y = np.asarray(y, dtype="float32") + + y /= np.max(y) + + return x[y > th], y[y > th] + + +class TritonPythonModel: + def __init__(self): + super().__init__() + self.output_dtype = None + + def initialize(self, args): + model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name(model_config, "intensities") + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + all_mzs = [] + all_ints = [] + for request in requests: + # mtx = pb_utils.get_input_tensor_by_name(request, "spectrum").as_numpy() + # np.save("mtx.npy", mtx) + spectra = pb_utils.get_input_tensor_by_name(request, "spectrum").as_numpy() + precursor_mass_with_oxm = pb_utils.get_input_tensor_by_name( + request, "precursor_mass_with_oxM" + ).as_numpy() + + # format this for whole batch. May require not prematurely cutting spectra short based on precursor mass + for i, mass in enumerate(precursor_mass_with_oxm): + spectra[i, min(math.ceil(mass / 0.1), spectra.shape[1]) :] = 0 + spectra = np.square(spectra) + + final_shape1 = 0 + for i in range(spectra.shape[0]): + spectrum = spectra[i, :] + imz = np.arange(0, spectra.shape[1], dtype="int32") * 0.1 + mzs, its = sparse(imz, spectrum) + all_mzs.append(mzs) + all_ints.append(its) + if its.size > final_shape1: + final_shape1 = its.size + + for i, (mzs, ints) in enumerate(zip(all_mzs, all_ints)): + minus_int_array = np.full(final_shape1 - mzs.size, -0.001) + minus_mz_array = np.full(final_shape1 - mzs.size, -1) + all_mzs[i] = np.append(mzs, minus_mz_array) + all_ints[i] = np.append(ints, minus_int_array) + + all_mzs = np.array(all_mzs) + all_ints = np.array(all_ints) + + all_ints *= 1000 + all_mzs = np.round(all_mzs, 2) + all_ints = np.round(all_ints, 4) + + output_tensors = [ + pb_utils.Tensor("mzs", all_mzs.astype(self.output_dtype)), + pb_utils.Tensor("intensities", all_ints.astype(self.output_dtype)), + ] + + responses.append(pb_utils.InferenceResponse(output_tensors=output_tensors)) + + return responses diff --git a/models/PredFull/PredFull_Postprocess/config.pbtxt b/models/PredFull/PredFull_Postprocess/config.pbtxt new file mode 100644 index 00000000..8e911112 --- /dev/null +++ b/models/PredFull/PredFull_Postprocess/config.pbtxt @@ -0,0 +1,26 @@ +max_batch_size: 1000 + +input[ + { + name: 'spectrum', + data_type: TYPE_FP32, + dims: [20000] + }, + { + name: 'precursor_mass_with_oxM', + data_type: TYPE_FP32, + dims: [1] + } +] +output [ + { + name: 'mzs', + data_type: TYPE_FP32, + dims: [-1] + }, + { + name: 'intensities', + data_type: TYPE_FP32, + dims: [-1] + } +] diff --git a/models/PredFull/PredFull_Preprocess_charge/1/model.py b/models/PredFull/PredFull_Preprocess_charge/1/model.py new file mode 100644 index 00000000..225e937b --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_charge/1/model.py @@ -0,0 +1,48 @@ +import json +import triton_python_backend_utils as pb_utils +import numpy as np + +MAX_CHARGE = 30 + + +def indices_to_one_hot(data, nb_classes): + """ + Convert an iterable of indices to one-hot encoded labels. + :param data: charge, int between 1 and 30 + """ + if data > nb_classes or data < 1: + raise RuntimeError("Charge out of range") + targets = np.array([data - 1]) # -1 for 0 indexing + return np.int_((np.eye(nb_classes)[targets])).tolist()[0] + + +def to_one_hot(numeric): + array = [indices_to_one_hot(x, MAX_CHARGE) for x in numeric] + return np.array(array, dtype=float) + + +class TritonPythonModel: + def initialize(self, args): + self.model_config = model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name( + self.model_config, "precursor_charges_in:0" + ) + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): # throw error if charge less than 1 or greater than 30 + responses = [] + for request in requests: + # at earliest sign of charge exceeding max, can append TritonError instead + charge_in_raw = pb_utils.get_input_tensor_by_name( + request, "precursor_charges" + ) + charge_in_flat = sum(charge_in_raw.as_numpy().tolist(), []) + charge_in = to_one_hot(charge_in_flat) + t = pb_utils.Tensor( + "precursor_charges_in:0", charge_in.astype(self.output_dtype) + ) + responses.append(pb_utils.InferenceResponse(output_tensors=[t])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_charge/config.pbtxt b/models/PredFull/PredFull_Preprocess_charge/config.pbtxt new file mode 100644 index 00000000..468fe52d --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_charge/config.pbtxt @@ -0,0 +1,15 @@ +max_batch_size: 1000 +input[ + { + name: 'precursor_charges', + data_type: TYPE_INT32, + dims: [1] + } +] +output [ + { + name: 'precursor_charges_in:0', + data_type: TYPE_FP32, + dims: [30] + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_Preprocess_collision_energy/1/model.py b/models/PredFull/PredFull_Preprocess_collision_energy/1/model.py new file mode 100644 index 00000000..240861e5 --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_collision_energy/1/model.py @@ -0,0 +1,33 @@ +import triton_python_backend_utils as pb_utils +import numpy as np +import json + + +class TritonPythonModel: + def initialize(self, args): + self.model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name( + self.model_config, "norm_collision_energy" + ) + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + for request in requests: + raw_ce = pb_utils.get_input_tensor_by_name( + request, "collision_energies" + ).as_numpy() + + for i in range(raw_ce.shape[0]): + if raw_ce[i] == 0: + raw_ce[i] = 25 + + t = pb_utils.Tensor( + "norm_collision_energy", (raw_ce / 100).astype(self.output_dtype) + ) + # will need to eventually cast to vector of length 30 + responses.append(pb_utils.InferenceResponse(output_tensors=[t])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_collision_energy/config.pbtxt b/models/PredFull/PredFull_Preprocess_collision_energy/config.pbtxt new file mode 100644 index 00000000..32ed3baf --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_collision_energy/config.pbtxt @@ -0,0 +1,15 @@ +max_batch_size: 1000 +input[ + { + name: 'collision_energies', + data_type: TYPE_FP32, + dims: [1] + } +] +output [ + { + name: 'norm_collision_energy', + data_type: TYPE_FP32, + dims: [1] + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_Preprocess_combine_meta/1/model.py b/models/PredFull/PredFull_Preprocess_combine_meta/1/model.py new file mode 100644 index 00000000..e037cfb6 --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_combine_meta/1/model.py @@ -0,0 +1,42 @@ +import json +import triton_python_backend_utils as pb_utils +import numpy as np + + +class TritonPythonModel: + def initialize(self, args): + self.model_config = model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name( + self.model_config, "meta_input" + ) + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + for request in requests: + charge = pb_utils.get_input_tensor_by_name( + request, "precursor_charges_in:0" + ).as_numpy() + fragmentation = pb_utils.get_input_tensor_by_name( + request, "fragmentation_types_encoding" + ).as_numpy() + mass = pb_utils.get_input_tensor_by_name( + request, "precursor_mass" + ).as_numpy() + nce = pb_utils.get_input_tensor_by_name( + request, "norm_collision_energy" + ).as_numpy() + + meta = np.zeros((charge.shape[0], 3, 30)) # testing + for i in range(charge.shape[0]): + meta[i, 0, :] = charge[i, :] + meta[i, 1, :] = fragmentation[i, :] + meta[i, 2, 0] = mass[i] + meta[i, 2, -1] = nce[i] + + t = pb_utils.Tensor("meta_input", meta.astype(self.output_dtype)) + responses.append(pb_utils.InferenceResponse(output_tensors=[t])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_combine_meta/config.pbtxt b/models/PredFull/PredFull_Preprocess_combine_meta/config.pbtxt new file mode 100644 index 00000000..8bed89d3 --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_combine_meta/config.pbtxt @@ -0,0 +1,30 @@ +max_batch_size: 1000 +input [ + { + name: 'precursor_charges_in:0', + data_type: TYPE_FP32, + dims: [30] + }, + { + name: 'fragmentation_types_encoding', + data_type: TYPE_FP32, + dims: [30], + }, + { + name: 'precursor_mass', + data_type: TYPE_FP32, + dims: [1] + }, + { + name: 'norm_collision_energy', + data_type: TYPE_FP32, + dims: [1] + } +] +output [ + { + name: 'meta_input', + data_type: TYPE_FP32, + dims: [3, 30] + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_Preprocess_fragmentation_types/1/model.py b/models/PredFull/PredFull_Preprocess_fragmentation_types/1/model.py new file mode 100644 index 00000000..f38bf1c6 --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_fragmentation_types/1/model.py @@ -0,0 +1,50 @@ +import triton_python_backend_utils as pb_utils +import numpy as np +import json + +ftypes = {"UN": 0, "CID": 1, "ETD": 2, "HCD": 3, "ETHCD": 4, "ETCID": 5} + + +def map_fragtypes(data): + datanum = ftypes[data] + targets = np.array([datanum]) + return np.int_((np.eye(30)[targets])).tolist()[0] + + +def to_one_hot(fragtypes): + array = [map_fragtypes(x) for x in fragtypes] + return np.array(array, dtype=float) + + +class TritonPythonModel: + def __init__(self): + super().__init__() + self.output_dtype = None + + def initialize(self, args): + model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name( + model_config, "fragmentation_types_encoding" + ) + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + for request in requests: + fragmentation_types = pb_utils.get_input_tensor_by_name( + request, "fragmentation_types" + ) + fragmentation_types = sum( + np.char.upper(fragmentation_types.as_numpy().astype(str)).tolist(), [] + ) + fragmentation_types_encoding = to_one_hot(fragmentation_types) + + t = pb_utils.Tensor( + "fragmentation_types_encoding", + fragmentation_types_encoding.astype(self.output_dtype), + ) + responses.append(pb_utils.InferenceResponse(output_tensors=[t])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_fragmentation_types/config.pbtxt b/models/PredFull/PredFull_Preprocess_fragmentation_types/config.pbtxt new file mode 100644 index 00000000..c7a168cb --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_fragmentation_types/config.pbtxt @@ -0,0 +1,15 @@ +max_batch_size: 1000 +input[ + { + name: 'fragmentation_types', + data_type: TYPE_STRING, + dims: [1] + } +] +output [ +{ + name: 'fragmentation_types_encoding', + data_type: TYPE_FP32, + dims: [30] + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_Preprocess_mass/1/model.py b/models/PredFull/PredFull_Preprocess_mass/1/model.py new file mode 100644 index 00000000..96ac8cb1 --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_mass/1/model.py @@ -0,0 +1,81 @@ +import triton_python_backend_utils as pb_utils +import numpy as np +import json +from pyteomics import mass + + +def getmod(pep): + mod = np.zeros(len(pep)) + + if pep.isalpha(): + return pep, mod + + seq = [] + + i = -1 + while len(pep) > 0: + if pep[0] == "[": + if pep[8:11] == "35]": + mod[i] = 1 + pep = pep[11:] + else: # not oxM + mod[i] = -1 + return pep, mod + else: + seq += pep[0] + pep = pep[1:] + i = len(seq) - 1 + + return "".join(seq), mod[: len(seq)] + + +class TritonPythonModel: + def __init__(self): + super().__init__() + self.output_dtype = None + + def initialize(self, args): + model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name( + model_config, "precursor_mass" + ) + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + for request in requests: + peptide_in = pb_utils.get_input_tensor_by_name(request, "peptide_sequences") + peptides_ = peptide_in.as_numpy().tolist() + peptide_in_list = [x[0].decode("utf-8") for x in peptides_] + masses = [] + masses_with_oxm = [] # for postprocessing + + for seq in peptide_in_list: + seq, mod = getmod(seq) # move getmod as separate python file + if np.any(mod == -1): + raise RuntimeError("Only Oxidation modification is supported") + + base = mass.fast_mass(seq, ion_type="M", charge=1) + base += 57.021 * seq.count("C") + base_with_oxm = base + 15.9949 * sum(mod) + base /= 20000.0 + masses.append(base) + masses_with_oxm.append(base_with_oxm) + + masses = np.array(masses) + # masses = np.reshape(masses, (1, 1)) # testing + masses_with_oxm = np.array(masses_with_oxm) + # masses_with_oxm = np.reshape(masses_with_oxm, (1, 1)) # testing + t = pb_utils.Tensor( + "precursor_mass", + masses.astype(self.output_dtype), + ) + t2 = pb_utils.Tensor( + "precursor_mass_with_oxM", + masses_with_oxm.astype(self.output_dtype), + ) + responses.append(pb_utils.InferenceResponse(output_tensors=[t, t2])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_mass/config.pbtxt b/models/PredFull/PredFull_Preprocess_mass/config.pbtxt new file mode 100644 index 00000000..a45a37bf --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_mass/config.pbtxt @@ -0,0 +1,22 @@ +max_batch_size: 1000 +input[ + { + name: 'peptide_sequences', + data_type: TYPE_STRING, + dims: [-1] + } +] +output [ + { + name: 'precursor_mass', + data_type: TYPE_FP32, + dims: [1] + reshape: {shape: []} + }, + { + name: 'precursor_mass_with_oxM', + data_type: TYPE_FP32, + dims: [1] + reshape: {shape: []} + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_Preprocess_peptide/1/model.py b/models/PredFull/PredFull_Preprocess_peptide/1/model.py new file mode 100644 index 00000000..0bcbda9c --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_peptide/1/model.py @@ -0,0 +1,121 @@ +import json +import numpy as np +import triton_python_backend_utils as pb_utils + +Alist = list("ACDEFGHIKLMNPQRSTVWYZ") +charMap = {"*": 0, "]": len(Alist) + 1, "[": len(Alist) + 2} +for i, a in enumerate(Alist): + charMap[a] = i + 1 + +ENCODING_DIMENSION = 24 + +mono = { + "G": 57.021464, + "A": 71.037114, + "S": 87.032029, + "P": 97.052764, + "V": 99.068414, + "T": 101.04768, + "C": 160.03019, + "L": 113.08406, + "I": 113.08406, + "D": 115.02694, + "Q": 128.05858, + "K": 128.09496, + "E": 129.04259, + "M": 131.04048, + "m": 147.0354, + "H": 137.05891, + "F": 147.06441, + "R": 156.10111, + "Y": 163.06333, + "N": 114.04293, + "W": 186.07931, + "O": 147.03538, +} + + +def getmod(pep): + mod = np.zeros(len(pep)) + + if pep.isalpha(): + return pep, mod + + seq = [] + + i = -1 + while len(pep) > 0: + if pep[0] == "[": + if pep[8:11] == "35]": + mod[i] = 1 + pep = pep[11:] + else: # not oxM + mod[i] = -1 + return pep, mod + else: + seq += pep[0] + pep = pep[1:] + i = len(seq) - 1 + + return "".join(seq), mod[: len(seq)] + + +class TritonPythonModel: + def initialize(self, args): + self.model_config = model_config = json.loads(args["model_config"]) + output0_config = pb_utils.get_output_config_by_name(self.model_config, "input") + self.output_dtype = pb_utils.triton_string_to_numpy(output0_config["data_type"]) + + def execute(self, requests): + responses = [] + + for request in requests: + sequences = [] + peptide_in = pb_utils.get_input_tensor_by_name(request, "peptide_sequences") + peptides_ = peptide_in.as_numpy().tolist() + peptide_in_list = [x[0].decode("utf-8") for x in peptides_] + + pep_dimension = 30 + for seq in peptide_in_list: # can make this more efficient + seq, mod = getmod(seq) + + if len(seq) + 2 > pep_dimension: + pep_dimension = len(seq) + 2 + + for seq in peptide_in_list: + # check for modifications + seq, mod = getmod(seq) + + embedding = np.zeros([pep_dimension, 29], dtype="float32") + + if np.any(mod == -1): + raise RuntimeError("Only Oxidation modification is supported") + + # process base peptide sequence + seq = seq.replace("L", "I") + embedding[len(seq)][ENCODING_DIMENSION - 1] = 1 # ending pos + for i, aa in enumerate(seq): + embedding[i][charMap[aa]] = 1 + embedding[i][ENCODING_DIMENSION] = ( + mono[aa] / 200 + ) # mass of AA divided by 200 + embedding[: len(seq), ENCODING_DIMENSION + 1] = ( + np.arange(len(seq)) / 1000 + ) # position info #position divided by 1000 + embedding[len(seq) + 1, 0] = 1 # padding info + + # still need to encode mod + for i, modi in enumerate(mod): + embedding[i][ENCODING_DIMENSION + 2 + int(modi)] = 1 + + sequences.append(embedding) + + sequences = np.array(sequences) + + t = pb_utils.Tensor("input", sequences.astype(self.output_dtype)) + + responses.append(pb_utils.InferenceResponse(output_tensors=[t])) + return responses + + def finalize(self): + pass diff --git a/models/PredFull/PredFull_Preprocess_peptide/config.pbtxt b/models/PredFull/PredFull_Preprocess_peptide/config.pbtxt new file mode 100644 index 00000000..16177d5f --- /dev/null +++ b/models/PredFull/PredFull_Preprocess_peptide/config.pbtxt @@ -0,0 +1,15 @@ +max_batch_size: 1000 +input[ + { + name: 'peptide_sequences', + data_type: TYPE_STRING, + dims: [-1] + } +] +output [ + { + name: 'input', + data_type: TYPE_FP32, + dims: [-1, 29] + } +] \ No newline at end of file diff --git a/models/PredFull/PredFull_core/1/.zenodo b/models/PredFull/PredFull_core/1/.zenodo new file mode 100644 index 00000000..ac748a53 --- /dev/null +++ b/models/PredFull/PredFull_core/1/.zenodo @@ -0,0 +1,2 @@ +https://zenodo.org/records/14675474/files/model.savedmodel.zip?download=1 +md5:803d8fc9cde2c0407f84f10064883726 diff --git a/models/PredFull/PredFull_core/config.pbtxt b/models/PredFull/PredFull_core/config.pbtxt new file mode 100644 index 00000000..9a8715bc --- /dev/null +++ b/models/PredFull/PredFull_core/config.pbtxt @@ -0,0 +1 @@ +max_batch_size: 1000