22import json
33import re
44from pyteomics import mass , parser
5- from itertools import tee
5+ from itertools import tee , product
66import ms_deisotope
77import logging
88
@@ -38,7 +38,8 @@ def fragment_annotation(
3838 psms : PSMList ,
3939 spectra_file : SpectrumFile ,
4040 tolerance : float ,
41- fragment_types : list [str ],
41+ nterm_fragment_types : list [str ],
42+ cterm_fragment_types : list [str ],
4243 charges : list [str ],
4344 losses : list [str ],
4445 deisotope : bool ,
@@ -48,7 +49,8 @@ def fragment_annotation(
4849 psms = psms ,
4950 spectra_file = spectra_file ,
5051 tolerance = tolerance ,
51- fragment_types = fragment_types ,
52+ nterm_fragment_types = nterm_fragment_types ,
53+ cterm_fragment_types = cterm_fragment_types ,
5254 charges = charges ,
5355 losses = losses ,
5456 deisotope = deisotope ,
@@ -62,7 +64,8 @@ def fragment_annotation(
6264 psms : PSMList ,
6365 spectra_file : SpectrumFile ,
6466 tolerance : float ,
65- fragment_types : list [str ],
67+ nterm_fragment_types : list [str ],
68+ cterm_fragment_types : list [str ],
6669 charges : list [str ] | str ,
6770 losses : list [str ],
6871 deisotope : bool ,
@@ -112,6 +115,8 @@ def fragment_annotation(
112115
113116 print (f"\n Annotating spectra in { 'parallel' if do_parallel else 'serial' } ...\n " )
114117
118+ internal_fragment_types = get_internal_ion_types (nterm_fragment_types , cterm_fragment_types )
119+
115120 if micro_batch :
116121 i = 0
117122 still_spectra_available = True
@@ -125,17 +130,17 @@ def fragment_annotation(
125130 current_batch = psms [i :len (psms )]
126131 still_spectra_available = False
127132 if do_parallel :
128- p_result = Parallel (n_jobs = nr_used_cores )(delayed (calculate_ions_for_psms )(psm , tolerance , fragment_types , charges , losses , deisotope ) for psm in current_batch )
133+ p_result = Parallel (n_jobs = nr_used_cores )(delayed (calculate_ions_for_psms )(psm , tolerance , nterm_fragment_types , cterm_fragment_types , internal_fragment_types , charges , losses , deisotope ) for psm in current_batch )
129134 else :
130- p_result = [calculate_ions_for_psms (psm , tolerance , fragment_types , charges , losses , deisotope ) for psm in current_batch ]
135+ p_result = [calculate_ions_for_psms (psm , tolerance , nterm_fragment_types , cterm_fragment_types , internal_fragment_types , charges , losses , deisotope ) for psm in current_batch ]
131136 psms_json += list (p_result )
132137 i += batch_size
133138 else :
134139 p_psms = tqdm (psms ) # tqdm is good for cli but bad for streamlit progress
135140 if do_parallel :
136- p_result = Parallel (n_jobs = nr_used_cores )(delayed (calculate_ions_for_psms )(psm , tolerance , fragment_types , charges , losses , deisotope ) for psm in p_psms )
141+ p_result = Parallel (n_jobs = nr_used_cores )(delayed (calculate_ions_for_psms )(psm , tolerance , nterm_fragment_types , cterm_fragment_types , internal_fragment_types , charges , losses , deisotope ) for psm in p_psms )
137142 else :
138- p_result = [calculate_ions_for_psms (psm , tolerance , fragment_types , charges , losses , deisotope ) for psm in p_psms ]
143+ p_result = [calculate_ions_for_psms (psm , tolerance , nterm_fragment_types , cterm_fragment_types , internal_fragment_types , charges , losses , deisotope ) for psm in p_psms ]
139144 psms_json = list (p_result )
140145
141146 for psm in psms_json :
@@ -160,9 +165,25 @@ def fragment_annotation(
160165 return psms_dict
161166
162167
168+ def get_internal_ion_types (nterm_fragment_types : list [str ], cterm_fragment_types : list [str ]) -> list [str ]:
169+ """Get non-redundant internal ion types based on provided n-term and c-term fragment types."""
170+ internal_ions = []
171+ equiv_internal_ions = set ()
172+ for n_frag , c_frag in product (nterm_fragment_types , cterm_fragment_types ):
173+ equiv = constants .IDENTICAL_INTERNAL_IONS [(n_frag , c_frag )]
174+ if equiv not in equiv_internal_ions :
175+ equiv_internal_ions .add (equiv )
176+ internal_ions .append (f"{ n_frag } :{ c_frag } " )
177+ else :
178+ logger .debug ("Skipping redundant internal ion: %s" , f"{ n_frag } :{ c_frag } " )
179+ return internal_ions
180+
181+
163182def calculate_ions_for_psms (psm ,
164183 tolerance : float ,
165- fragment_types : list [str ],
184+ nterm_fragment_types : list [str ],
185+ cterm_fragment_types : list [str ],
186+ internal_fragment_types : list [str ],
166187 charges : list [str ] | str ,
167188 losses : list [str ],
168189 deisotope : bool ) -> dict [str , Any ]:
@@ -182,11 +203,12 @@ def calculate_ions_for_psms(psm,
182203
183204 theoretical_fragment_code = compute_theoretical_fragments (
184205 sequence_length = len (psm .peptidoform .sequence ),
185- fragment_types = typed .List (fragment_types ),
206+ n_term_ions = typed .List (nterm_fragment_types ),
207+ c_term_ions = typed .List (cterm_fragment_types ),
186208 charges = typed .List ([int (c ) for c in charges_used ]),
187209 neutral_losses = typed .List (losses ),
188- ion_directions = ion_directions ,
189- internal = True
210+ # ion_directions=ion_directions,
211+ internal_ions = typed . List ( internal_fragment_types ),
190212 )
191213
192214 theoretical_fragment_dict = {
@@ -234,14 +256,16 @@ def deisotope_peak_list(mzs: list[float], intensities: list[float]) -> tuple[lis
234256@jit (nopython = True , cache = True )
235257def compute_theoretical_fragments (
236258 sequence_length : int ,
237- fragment_types : list [str ],
259+ n_term_ions : list [str ],
260+ c_term_ions : list [str ],
238261 charges : list [int ] = [1 ],
239262 neutral_losses : list [str ] = [],
240- ion_directions : dict [str , str ] = {},
241- internal : bool = True ) -> list [str ]:
263+ # ion_directions: dict[str, str] = {},
264+ internal_ions : list [str ] = [],
265+ ) -> list [str ]:
242266
243- n_term_ions = [ion_type for ion_type in fragment_types if ion_directions [ion_type ] == "n-term" ]
244- c_term_ions = [ion_type for ion_type in fragment_types if ion_directions [ion_type ] == "c-term" ]
267+ # n_term_ions = [ion_type for ion_type in fragment_types if ion_directions[ion_type] == "n-term"]
268+ # c_term_ions = [ion_type for ion_type in fragment_types if ion_directions[ion_type] == "c-term"]
245269
246270 n_term = ["t:" + ion_type for ion_type in n_term_ions ]
247271 c_term = [ion_type + ":t" for ion_type in c_term_ions ]
@@ -276,20 +300,18 @@ def compute_theoretical_fragments(
276300
277301 internal_frags_with_nl = ["" for _ in range (0 )] # typed empty list for Numba
278302
279- if internal :
303+ if internal_ions :
280304 # internal fragments
281- internal_frags = [
282- f"{ n_term_ion } :{ c_term_ion } " for n_term_ion in n_term_ions for c_term_ion in c_term_ions
283- ]
305+
284306 internal_pos = [
285307 f"{ i } :{ j } "
286308 for i in range (2 , sequence_length )
287309 for j in range (2 , sequence_length )
288310 if i <= j
289311 ]
290312 internal_frags = [
291- f"{ internal_ions } @{ internal_positions } "
292- for internal_ions in internal_frags
313+ f"{ ions } @{ internal_positions } "
314+ for ions in internal_ions
293315 for internal_positions in internal_pos
294316 ]
295317
0 commit comments