corems.molecular_id.search.molecularFormulaSearch
1__author__ = "Yuri E. Corilo" 2__date__ = "Jul 29, 2019" 3 4 5from typing import List 6 7import tqdm 8 9from corems import chunks, timeit 10from corems.encapsulation.constant import Labels 11from corems.molecular_formula.factory.MolecularFormulaFactory import ( 12 LCMSLibRefMolecularFormula, 13 MolecularFormula, 14) 15from corems.molecular_id.factory.MolecularLookupTable import MolecularCombinations 16from corems.molecular_id.factory.molecularSQL import MolForm_SQL 17from corems.ms_peak.factory.MSPeakClasses import _MSPeak 18 19last_error = 0 20last_dif = 0 21closest_error = 0 22error_average = 0 23nbValues = 0 24 25 26class SearchMolecularFormulas: 27 """Class for searching molecular formulas in a mass spectrum. 28 29 Parameters 30 ---------- 31 mass_spectrum_obj : MassSpectrum 32 The mass spectrum object. 33 sql_db : MolForm_SQL, optional 34 The SQL database object, by default None. 35 first_hit : bool, optional 36 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 37 find_isotopologues : bool, optional 38 Flag to indicate whether to find isotopologues, by default True. 39 40 Attributes 41 ---------- 42 mass_spectrum_obj : MassSpectrum 43 The mass spectrum object. 44 sql_db : MolForm_SQL 45 The SQL database object. 46 first_hit : bool 47 Flag to indicate whether to skip peaks that already have a molecular formula assigned. 48 find_isotopologues : bool 49 Flag to indicate whether to find isotopologues. 50 51 52 Methods 53 ------- 54 * run_search(). 55 Run the molecular formula search. 56 * run_worker_mass_spectrum(). 57 Run the molecular formula search on the mass spectrum object. 58 * run_worker_ms_peaks(). 59 Run the molecular formula search on the given list of mass spectrum peaks. 60 * database_to_dict(). 61 Convert the database results to a dictionary. 62 * run_molecular_formula(). 63 Run the molecular formula search on the given list of mass spectrum peaks. 64 * search_mol_formulas(). 65 Search for molecular formulas in the mass spectrum. 66 67 """ 68 69 def __init__( 70 self, 71 mass_spectrum_obj, 72 sql_db=None, 73 first_hit: bool = False, 74 find_isotopologues: bool = True, 75 ): 76 self.first_hit = first_hit 77 78 self.find_isotopologues = find_isotopologues 79 80 self.mass_spectrum_obj = mass_spectrum_obj 81 82 if not sql_db: 83 self.sql_db = MolForm_SQL( 84 url=mass_spectrum_obj.molecular_search_settings.url_database 85 ) 86 87 else: 88 self.sql_db = sql_db 89 90 def __enter__(self): 91 """Open the SQL database connection.""" 92 return self 93 94 def __exit__(self, exc_type, exc_val, exc_tb): 95 """Close the SQL database connection.""" 96 self.sql_db.close() 97 98 return False 99 100 def run_search( 101 self, 102 mspeaks: list, 103 query: dict, 104 min_abundance: float, 105 ion_type: str, 106 ion_charge: int, 107 adduct_atom=None, 108 ): 109 """Run the molecular formula search. 110 111 Parameters 112 ---------- 113 mspeaks : list of MSPeak 114 The list of mass spectrum peaks. 115 query : dict 116 The query dictionary containing the possible molecular formulas. 117 min_abundance : float 118 The minimum abundance threshold. 119 ion_type : str 120 The ion type. 121 ion_charge : int 122 The ion charge. 123 adduct_atom : str, optional 124 The adduct atom, by default None. 125 """ 126 127 def get_formulas(nominal_overlay: float = 0.1): 128 """ 129 Get the list of formulas based on the nominal overlay. 130 131 Parameters 132 ---------- 133 nominal_overlay : float, optional 134 The nominal overlay, by default 0.1. 135 136 Returns 137 ------- 138 list 139 The list of formulas. 140 """ 141 nominal_mz = ms_peak.nominal_mz_exp 142 143 defect_mass = ms_peak.mz_exp - nominal_mz 144 nominal_masses = [nominal_mz] 145 146 if (defect_mass) >= 1 - nominal_overlay: 147 nominal_masses.append(nominal_mz + 1) 148 elif (defect_mass) <= nominal_overlay: 149 nominal_masses.append(nominal_mz - 1) 150 151 list_formulas_candidates = [] 152 153 for nominal_mass in nominal_masses: 154 if nominal_mass in query.keys(): 155 list_formulas_candidates.extend(query.get(nominal_mass)) 156 157 return list_formulas_candidates 158 159 all_assigned_indexes = list() 160 161 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 162 163 search_molfrom = SearchMolecularFormulaWorker( 164 find_isotopologues=self.find_isotopologues 165 ) 166 167 for ms_peak in mspeaks: 168 # already assigned a molecular formula 169 if self.first_hit: 170 if ms_peak.is_assigned: 171 continue 172 173 ms_peak_indexes = search_molfrom.find_formulas( 174 get_formulas(), 175 min_abundance, 176 self.mass_spectrum_obj, 177 ms_peak, 178 ion_type, 179 ion_charge, 180 adduct_atom, 181 ) 182 183 all_assigned_indexes.extend(ms_peak_indexes) 184 185 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 186 187 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 188 189 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 190 # filter per min peaks per mono isotopic class 191 192 def run_worker_mass_spectrum(self): 193 """Run the molecular formula search on the mass spectrum object.""" 194 self.run_molecular_formula( 195 self.mass_spectrum_obj.sort_by_abundance(), 196 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 197 ) 198 199 def run_worker_ms_peaks(self, ms_peaks): 200 """Run the molecular formula search on the given list of mass spectrum peaks. 201 202 Parameters 203 ---------- 204 ms_peaks : list of MSPeak 205 The list of mass spectrum peaks. 206 """ 207 self.run_molecular_formula( 208 ms_peaks, 209 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 210 ) 211 212 @staticmethod 213 def database_to_dict( 214 classe_str_list, 215 nominal_mzs, 216 mf_search_settings, 217 ion_charge, 218 sql_db=None, 219 ): 220 """Convert the database results to a dictionary. 221 222 Parameters 223 ---------- 224 classe_str_list : list 225 The list of class strings. 226 nominal_mzs : list 227 The list of nominal m/z values. 228 mf_search_settings : MolecularFormulaSearchSettings 229 The molecular formula search settings. 230 ion_charge : int 231 The ion charge. 232 sql_db : MolForm_SQL, optional 233 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 234 235 Returns 236 ------- 237 dict 238 The dictionary containing the database results. 239 """ 240 owns_db = sql_db is None 241 if owns_db: 242 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 243 244 try: 245 dict_res = {} 246 247 if mf_search_settings.isProtonated: 248 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 249 classe_str_list, 250 Labels.protonated_de_ion, 251 nominal_mzs, 252 ion_charge, 253 mf_search_settings, 254 ) 255 256 if mf_search_settings.isRadical: 257 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 258 classe_str_list, 259 Labels.radical_ion, 260 nominal_mzs, 261 ion_charge, 262 mf_search_settings, 263 ) 264 265 if mf_search_settings.isAdduct: 266 adduct_list = ( 267 mf_search_settings.adduct_atoms_neg 268 if ion_charge < 0 269 else mf_search_settings.adduct_atoms_pos 270 ) 271 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 272 classe_str_list, 273 Labels.adduct_ion, 274 nominal_mzs, 275 ion_charge, 276 mf_search_settings, 277 adducts=adduct_list, 278 ) 279 280 return dict_res 281 finally: 282 if owns_db: 283 sql_db.close() 284 285 @timeit(print_time=True) 286 def run_molecular_formula(self, ms_peaks, **kwargs): 287 """Run the molecular formula search on the given list of mass spectrum peaks. 288 289 Parameters 290 ---------- 291 ms_peaks : list of MSPeak 292 The list of mass spectrum peaks. 293 **kwargs 294 Additional keyword arguments. 295 Most notably, print_time, which is a boolean flag to indicate whether to print the time 296 and passed to the timeit decorator. 297 """ 298 ion_charge = self.mass_spectrum_obj.polarity 299 min_abundance = self.mass_spectrum_obj.min_abundance 300 nominal_mzs = self.mass_spectrum_obj.nominal_mz 301 302 verbose = self.mass_spectrum_obj.molecular_search_settings.verbose_processing 303 # reset average error, only relevant is average mass error method is being used 304 SearchMolecularFormulaWorker( 305 find_isotopologues=self.find_isotopologues 306 ).reset_error(self.mass_spectrum_obj) 307 308 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 309 classes = MolecularCombinations(self.sql_db).runworker( 310 self.mass_spectrum_obj.molecular_search_settings, 311 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 312 ) 313 314 # split the database load to not blowout the memory 315 # TODO add to the settings 316 for classe_chunk in chunks( 317 classes, self.mass_spectrum_obj.molecular_search_settings.db_chunk_size 318 ): 319 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 320 321 # load the molecular formula objs binned by ion type and heteroatoms classes, {ion type:{classe:[list_formula]}} 322 # for adduct ion type a third key is added {atoms:{ion type:{classe:[list_formula]}}} 323 dict_res = self.database_to_dict( 324 classes_str_list, 325 nominal_mzs, 326 self.mass_spectrum_obj.molecular_search_settings, 327 ion_charge, 328 sql_db=self.sql_db, 329 ) 330 pbar = tqdm.tqdm(classe_chunk, disable = not verbose) 331 for classe_tuple in pbar: 332 # class string is a json serialized dict 333 classe_str = classe_tuple[0] 334 classe_dict = classe_tuple[1] 335 336 if self.mass_spectrum_obj.molecular_search_settings.isProtonated: 337 ion_type = Labels.protonated_de_ion 338 if verbose: 339 pbar.set_description_str( 340 desc="Started molecular formula search for class %s, (de)protonated " 341 % classe_str, 342 refresh=True, 343 ) 344 345 candidate_formulas = dict_res.get(ion_type).get(classe_str) 346 347 if candidate_formulas: 348 self.run_search( 349 ms_peaks, 350 candidate_formulas, 351 min_abundance, 352 ion_type, 353 ion_charge, 354 ) 355 356 if self.mass_spectrum_obj.molecular_search_settings.isRadical: 357 if verbose: 358 pbar.set_description_str( 359 desc="Started molecular formula search for class %s, radical " 360 % classe_str, 361 refresh=True, 362 ) 363 364 ion_type = Labels.radical_ion 365 366 candidate_formulas = dict_res.get(ion_type).get(classe_str) 367 368 if candidate_formulas: 369 self.run_search( 370 ms_peaks, 371 candidate_formulas, 372 min_abundance, 373 ion_type, 374 ion_charge, 375 ) 376 # looks for adduct, used_atom_valences should be 0 377 # this code does not support H exchance by halogen atoms 378 if self.mass_spectrum_obj.molecular_search_settings.isAdduct: 379 if verbose: 380 pbar.set_description_str( 381 desc="Started molecular formula search for class %s, adduct " 382 % classe_str, 383 refresh=True, 384 ) 385 386 ion_type = Labels.adduct_ion 387 dict_atoms_formulas = dict_res.get(ion_type) 388 389 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 390 candidate_formulas = dict_by_class.get(classe_str) 391 392 if candidate_formulas: 393 self.run_search( 394 ms_peaks, 395 candidate_formulas, 396 min_abundance, 397 ion_type, 398 ion_charge, 399 adduct_atom=adduct_atom, 400 ) 401 self.sql_db.close() 402 403 def search_mol_formulas( 404 self, 405 possible_formulas_list: List[MolecularFormula], 406 ion_type: str, 407 neutral_molform=True, 408 find_isotopologues=True, 409 adduct_atom=None, 410 ) -> List[_MSPeak]: 411 """Search for molecular formulas in the mass spectrum. 412 413 Parameters 414 ---------- 415 possible_formulas_list : list of MolecularFormula 416 The list of possible molecular formulas. 417 ion_type : str 418 The ion type. 419 neutral_molform : bool, optional 420 Flag to indicate whether the molecular formulas are neutral, by default True. 421 find_isotopologues : bool, optional 422 Flag to indicate whether to find isotopologues, by default True. 423 adduct_atom : str, optional 424 The adduct atom, by default None. 425 426 Returns 427 ------- 428 list of MSPeak 429 The list of mass spectrum peaks with assigned molecular formulas. 430 """ 431 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 432 # if that is the case than turn neutral_molform off 433 434 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 435 self.mass_spectrum_obj 436 ) 437 438 initial_min_peak_bool = ( 439 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 440 ) 441 initial_runtime_kendrick_filter = ( 442 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 443 ) 444 445 # Are the following 3 lines redundant? 446 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 447 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 448 False # TODO check this line 449 ) 450 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 451 False 452 ) 453 454 possible_formulas_dict_nm = {} 455 456 for mf in possible_formulas_list: 457 if neutral_molform: 458 nm = int(mf.protonated_mz) 459 else: 460 nm = int(mf.mz_nominal_calc) 461 462 if nm in possible_formulas_dict_nm.keys(): 463 possible_formulas_dict_nm[nm].append(mf) 464 465 else: 466 possible_formulas_dict_nm[nm] = [mf] 467 468 min_abundance = self.mass_spectrum_obj.min_abundance 469 470 ion_type = ion_type 471 472 self.run_search( 473 self.mass_spectrum_obj, 474 possible_formulas_dict_nm, 475 min_abundance, 476 ion_type, 477 self.mass_spectrum_obj.polarity, 478 adduct_atom=adduct_atom, 479 ) 480 481 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 482 initial_min_peak_bool 483 ) 484 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 485 initial_runtime_kendrick_filter 486 ) 487 488 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 489 490 self.sql_db.close() 491 492 return mspeaks 493 494 495class SearchMolecularFormulaWorker: 496 """Class for searching molecular formulas in a mass spectrum. 497 498 Parameters 499 ---------- 500 find_isotopologues : bool, optional 501 Flag to indicate whether to find isotopologues, by default True. 502 503 Attributes 504 ---------- 505 find_isotopologues : bool 506 Flag to indicate whether to find isotopologues. 507 508 Methods 509 ------- 510 * reset_error(). 511 Reset the error variables. 512 * set_last_error(). 513 Set the last error. 514 * find_formulas(). 515 Find the formulas. 516 * calc_error(). 517 Calculate the error. 518 """ 519 520 # TODO add reset error function 521 # needs this wraper to pass the class to multiprocessing 522 523 def __init__(self, find_isotopologues=True): 524 self.find_isotopologues = find_isotopologues 525 526 def __call__(self, args): 527 """Call the find formulas function. 528 529 Parameters 530 ---------- 531 args : tuple 532 The arguments. 533 534 Returns 535 ------- 536 list 537 The list of mass spectrum peaks with assigned molecular formulas. 538 """ 539 return self.find_formulas(*args) # ,args[1] 540 541 def reset_error(self, mass_spectrum_obj): 542 """Reset the error variables. 543 544 Parameters 545 ---------- 546 mass_spectrum_obj : MassSpectrum 547 The mass spectrum object. 548 549 Notes 550 ----- 551 This function resets the error variables for the given mass spectrum object. 552 """ 553 global last_error, last_dif, closest_error, error_average, nbValues 554 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0 555 556 def set_last_error(self, error, mass_spectrum_obj): 557 """Set the last error. 558 559 Parameters 560 ---------- 561 error : float 562 The error. 563 mass_spectrum_obj : MassSpectrum 564 The mass spectrum object. 565 """ 566 # set the changes to the global variables, not internal ones 567 global last_error, last_dif, closest_error, error_average, nbValues 568 569 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 570 dif = error - last_error 571 if dif < last_dif: 572 last_dif = dif 573 closest_error = error 574 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 575 closest_error 576 - mass_spectrum_obj.molecular_search_settings.mz_error_range 577 ) 578 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 579 closest_error 580 + mass_spectrum_obj.molecular_search_settings.mz_error_range 581 ) 582 583 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 584 if error < last_error: 585 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 586 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 587 ) 588 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 589 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 590 ) 591 last_error = error 592 593 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 594 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 595 mass_spectrum_obj.molecular_search_settings.mz_error_average 596 - mass_spectrum_obj.molecular_search_settings.mz_error_range 597 ) 598 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 599 mass_spectrum_obj.molecular_search_settings.mz_error_average 600 + mass_spectrum_obj.molecular_search_settings.mz_error_range 601 ) 602 603 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 604 nbValues += 1 605 error_average = error_average + ((error - error_average) / nbValues) 606 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 607 error_average 608 - mass_spectrum_obj.molecular_search_settings.mz_error_range 609 ) 610 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 611 error_average 612 + mass_spectrum_obj.molecular_search_settings.mz_error_range 613 ) 614 615 else: 616 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 617 pass 618 619 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method 620 621 @staticmethod 622 def calc_error(mz_exp, mz_calc, method="ppm"): 623 """Calculate the error. 624 625 Parameters 626 ---------- 627 mz_exp : float 628 The experimental m/z value. 629 mz_calc : float 630 The calculated m/z value. 631 method : str, optional 632 The method, by default 'ppm'. 633 634 Raises 635 ------- 636 Exception 637 If the method is not ppm or ppb. 638 639 Returns 640 ------- 641 float 642 The error. 643 """ 644 645 if method == "ppm": 646 multi_factor = 1_000_000 647 648 elif method == "ppb": 649 multi_factor = 1_000_000_000 650 651 elif method == "perc": 652 multi_factor = 100 653 654 else: 655 raise Exception( 656 "method needs to be ppm or ppb, you have entered %s" % method 657 ) 658 659 if mz_exp: 660 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 661 662 else: 663 raise Exception("Please set mz_calc first") 664 665 def find_formulas( 666 self, 667 formulas, 668 min_abundance, 669 mass_spectrum_obj, 670 ms_peak, 671 ion_type, 672 ion_charge, 673 adduct_atom=None, 674 ): 675 """Find the formulas. 676 677 Parameters 678 ---------- 679 formulas : list of MolecularFormula 680 The list of molecular formulas. 681 min_abundance : float 682 The minimum abundance threshold. 683 mass_spectrum_obj : MassSpectrum 684 The mass spectrum object. 685 ms_peak : MSPeak 686 The mass spectrum peak. 687 ion_type : str 688 The ion type. 689 ion_charge : int 690 The ion charge. 691 adduct_atom : str, optional 692 The adduct atom, by default None. 693 694 Returns 695 ------- 696 list of MSPeak 697 The list of mass spectrum peaks with assigned molecular formulas. 698 699 Notes 700 ----- 701 Uses the closest error the next search (this is not ideal, it needs to use confidence 702 metric to choose the right candidate then propagate the error using the error from the best candidate). 703 It needs to add s/n to the equation. 704 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 705 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 706 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 707 Method='distance' 708 """ 709 mspeak_assigned_index = list() 710 711 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 712 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 713 714 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 715 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 716 717 # f = open("abundance_error.txt", "a+") 718 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 719 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 720 721 def mass_by_ion_type(possible_formula_obj): 722 if ion_type == Labels.protonated_de_ion: 723 return possible_formula_obj._protonated_mz(ion_charge) 724 725 elif ion_type == Labels.radical_ion: 726 return possible_formula_obj._radical_mz(ion_charge) 727 728 elif ion_type == Labels.adduct_ion and adduct_atom: 729 return possible_formula_obj._adduct_mz(ion_charge, adduct_atom) 730 731 else: 732 # will return externally calculated mz if is set, #use on Bruker Reference list import 733 # if the ion type is known the ion mass based on molecular formula ion type 734 # if ion type is unknow will return neutral mass 735 return possible_formula_obj.mz_calc 736 737 if formulas: 738 if isinstance(formulas[0], LCMSLibRefMolecularFormula): 739 possible_mf_class = True 740 741 else: 742 possible_mf_class = False 743 744 for possible_formula in formulas: 745 if possible_formula: 746 error = self.calc_error( 747 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 748 ) 749 750 # error = possible_formula.mz_error 751 752 if min_ppm_error <= error <= max_ppm_error: 753 # update the error 754 755 self.set_last_error(error, mass_spectrum_obj) 756 757 # add molecular formula match to ms_peak 758 759 # get molecular formula dict from sql obj 760 # formula_dict = pickle.loads(possible_formula.mol_formula) 761 # if possible_mf_class: 762 763 # molecular_formula = deepcopy(possible_formula) 764 765 # else: 766 767 formula_dict = possible_formula.to_dict() 768 # create the molecular formula obj to be stored 769 if possible_mf_class: 770 molecular_formula = LCMSLibRefMolecularFormula( 771 formula_dict, 772 ion_charge, 773 ion_type=ion_type, 774 adduct_atom=adduct_atom, 775 ) 776 777 molecular_formula.name = possible_formula.name 778 molecular_formula.kegg_id = possible_formula.kegg_id 779 molecular_formula.cas = possible_formula.cas 780 781 else: 782 molecular_formula = MolecularFormula( 783 formula_dict, 784 ion_charge, 785 ion_type=ion_type, 786 adduct_atom=adduct_atom, 787 ) 788 # add the molecular formula obj to the mspeak obj 789 # add the mspeak obj and it's index for tracking next assignment step 790 791 if self.find_isotopologues: 792 # calculates isotopologues 793 isotopologues = molecular_formula.isotopologues( 794 min_abundance, 795 ms_peak_abundance, 796 mass_spectrum_obj.dynamic_range, 797 ) 798 799 # search for isotopologues 800 for isotopologue_formula in isotopologues: 801 molecular_formula.expected_isotopologues.append( 802 isotopologue_formula 803 ) 804 # move this outside to improve preformace 805 # we need to increase the search space to -+1 m_z 806 first_index, last_index = ( 807 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 808 isotopologue_formula.mz_nominal_calc 809 ) 810 ) 811 812 for ms_peak_iso in mass_spectrum_obj[ 813 first_index:last_index 814 ]: 815 error = self.calc_error( 816 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 817 ) 818 819 if min_ppm_error <= error <= max_ppm_error: 820 # need to define error distribution for abundance measurements 821 822 # if mass_spectrum_obj.is_centroid: 823 824 abundance_error = self.calc_error( 825 isotopologue_formula.abundance_calc, 826 ms_peak_iso.abundance, 827 method="perc", 828 ) 829 830 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 831 832 # margin of error was set empirically/ needs statistical calculation 833 # of margin of error for the measurement of the abundances 834 if ( 835 min_abun_error 836 <= abundance_error 837 <= max_abun_error 838 ): 839 # update the error 840 841 self.set_last_error(error, mass_spectrum_obj) 842 843 # isotopologue_formula.mz_error = error 844 845 # isotopologue_formula.area_error = area_error 846 847 # isotopologue_formula.abundance_error = abundance_error 848 849 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 850 851 mono_isotopic_formula_index = len(ms_peak) 852 853 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 854 855 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 856 857 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 858 859 # add molecular formula match to ms_peak 860 x = ms_peak_iso.add_molecular_formula( 861 isotopologue_formula 862 ) 863 864 molecular_formula.mspeak_mf_isotopologues_indexes.append( 865 (ms_peak_iso.index, x) 866 ) 867 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 868 869 y = ms_peak.add_molecular_formula(molecular_formula) 870 871 mspeak_assigned_index.append((ms_peak.index, y)) 872 873 return mspeak_assigned_index 874 875 876class SearchMolecularFormulasLC: 877 """Class for searching molecular formulas in a LC object. 878 879 Parameters 880 ---------- 881 lcms_obj : LCMSBase 882 The LCMSBase object. 883 sql_db : MolForm_SQL, optional 884 The SQL database object, by default None. 885 first_hit : bool, optional 886 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 887 find_isotopologues : bool, optional 888 Flag to indicate whether to find isotopologues, by default True. 889 890 Methods 891 ------- 892 893 * search_spectra_against_candidates(). 894 Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 895 * bulk_run_molecular_formula_search(). 896 Run the molecular formula search on the given list of mass spectra. 897 Pulls the settings from the LCMSBase object to set ion type and charge to search for. 898 * run_mass_feature_search(). 899 Run the molecular formula search on mass features. 900 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 901 * run_untargeted_worker_ms1(). 902 Run untargeted molecular formula search on the ms1 mass spectrum. 903 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 904 * run_target_worker_ms1(). 905 Run targeted molecular formula search on the ms1 mass spectrum. 906 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 907 """ 908 909 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 910 self.first_hit = first_hit 911 912 self.find_isotopologues = find_isotopologues 913 914 self.lcms_obj = lcms_obj 915 916 if not sql_db: 917 self.sql_db = MolForm_SQL( 918 url=self.lcms_obj.parameters.mass_spectrum['ms1'].molecular_search.url_database 919 ) 920 921 else: 922 self.sql_db = sql_db 923 924 def search_spectra_against_candidates(self, mass_spectrum_list, ms_peaks_list, candidate_formulas, ion_type, ion_charge): 925 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 926 927 Parameters 928 ---------- 929 mass_spectrum_list : list of MassSpectrum 930 The list of mass spectra to perform the search on. 931 ms_peaks_list : list of lists of MSPeak objects 932 The list of mass spectrum peaks to search within each mass spectrum. 933 candidate_formulas : dict 934 The candidate formulas. 935 ion_type : str 936 The ion type. 937 ion_charge : int 938 The ion charge, either 1 or -1. 939 940 Notes 941 ----- 942 This function is designed to be used with the bulk_run_molecular_formula_search function. 943 """ 944 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 945 single_ms_search = SearchMolecularFormulas( 946 mass_spectrum, 947 sql_db=self.sql_db, 948 first_hit=self.first_hit, 949 find_isotopologues=self.find_isotopologues, 950 ) 951 single_ms_search.run_search( 952 ms_peaks, 953 candidate_formulas, 954 mass_spectrum.min_abundance, 955 ion_type, 956 ion_charge, 957 ) 958 959 def bulk_run_molecular_formula_search(self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key='ms1'): 960 """Run the molecular formula search on the given list of mass spectra 961 962 Parameters 963 ---------- 964 mass_spectrum_list : list of MassSpectrum 965 The list of mass spectra to search. 966 ms_peaks_list : list of lists of MSPeak objects 967 The mass peaks to perform molecular formula search within each mass spectrum 968 mass_spectrum_setting_key : str, optional 969 The mass spectrum setting key, by default 'ms1'. 970 This is used to get the appropriate molecular search settings from the LCMSBase object 971 """ 972 # Set min_abundance and nominal_mzs 973 if self.lcms_obj.polarity == "positive": 974 ion_charge = 1 975 elif self.lcms_obj.polarity == "negative": 976 ion_charge = -1 977 else: 978 raise ValueError("Polarity must be either 'positive' or 'negative'") 979 980 # Check that the length of the mass spectrum list and the ms_peaks list are the same 981 if len(mass_spectrum_list) != len(ms_peaks_list): 982 raise ValueError("The length of the mass spectrum list and the ms_peaks list must be the same") 983 984 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 985 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 986 verbose = self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 987 988 # reset average error, only relevant if average mass error method is being used 989 SearchMolecularFormulaWorker( 990 find_isotopologues=self.find_isotopologues 991 ).reset_error(mass_spectrum_list[0]) 992 993 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 994 classes = MolecularCombinations(self.sql_db).runworker( 995 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 996 print_time=self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 997 ) 998 999 try: 1000 # split the database load to not blowout the memory 1001 for classe_chunk in chunks( 1002 classes, self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.db_chunk_size 1003 ): 1004 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 1005 1006 # load the molecular formula objs binned by ion type and heteroatoms classes, {ion type:{classe:[list_formula]}} 1007 # for adduct ion type a third key is added {atoms:{ion type:{classe:[list_formula]}}} 1008 dict_res = SearchMolecularFormulas.database_to_dict( 1009 classes_str_list, 1010 nominal_mzs, 1011 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 1012 ion_charge, 1013 sql_db=self.sql_db, 1014 ) 1015 1016 pbar = tqdm.tqdm(classe_chunk, disable=not verbose) 1017 for classe_tuple in pbar: 1018 # class string is a json serialized dict 1019 classe_str = classe_tuple[0] 1020 1021 # Perform search for (de)protonated ion type 1022 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isProtonated: 1023 ion_type = Labels.protonated_de_ion 1024 1025 pbar.set_description_str( 1026 desc="Started molecular formula search for class %s, (de)protonated " 1027 % classe_str, 1028 refresh=True, 1029 ) 1030 1031 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1032 1033 if candidate_formulas: 1034 self.search_spectra_against_candidates( 1035 mass_spectrum_list=mass_spectrum_list, 1036 ms_peaks_list=ms_peaks_list, 1037 candidate_formulas=candidate_formulas, 1038 ion_type=ion_type, 1039 ion_charge=ion_charge, 1040 ) 1041 1042 # Perform search for radical ion type 1043 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isRadical: 1044 pbar.set_description_str( 1045 desc="Started molecular formula search for class %s, radical " 1046 % classe_str, 1047 refresh=True, 1048 ) 1049 1050 ion_type = Labels.radical_ion 1051 1052 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1053 1054 if candidate_formulas: 1055 self.search_spectra_against_candidates( 1056 mass_spectrum_list=mass_spectrum_list, 1057 ms_peaks_list=ms_peaks_list, 1058 candidate_formulas=candidate_formulas, 1059 ion_type=ion_type, 1060 ion_charge=ion_charge, 1061 ) 1062 1063 # Perform search for adduct ion type 1064 # looks for adduct, used_atom_valences should be 0 1065 # this code does not support H exchance by halogen atoms 1066 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isAdduct: 1067 pbar.set_description_str( 1068 desc="Started molecular formula search for class %s, adduct " 1069 % classe_str, 1070 refresh=True, 1071 ) 1072 1073 ion_type = Labels.adduct_ion 1074 dict_atoms_formulas = dict_res.get(ion_type) 1075 1076 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 1077 candidate_formulas = dict_by_class.get(classe_str) 1078 1079 if candidate_formulas: 1080 self.search_spectra_against_candidates( 1081 mass_spectrum_list=mass_spectrum_list, 1082 ms_peaks_list=ms_peaks_list, 1083 candidate_formulas=candidate_formulas, 1084 ion_type=ion_type, 1085 ion_charge=ion_charge, 1086 ) 1087 finally: 1088 self.sql_db.close() 1089 1090 def run_mass_feature_search(self): 1091 """Run the molecular formula search on the mass features. 1092 1093 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1094 """ 1095 mass_features_df = self.lcms_obj.mass_features_to_df() 1096 1097 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1098 scan_list = mass_features_df.apex_scan.unique() 1099 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1100 ms_peaks = [] 1101 for scan in scan_list: 1102 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1103 peaks_to_search = [ 1104 self.lcms_obj.mass_features[x].ms1_peak for x in mf_df_scan.index.tolist() 1105 ] 1106 ms_peaks.append(peaks_to_search) 1107 1108 # Run the molecular formula search 1109 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks) 1110 1111 def run_untargeted_worker_ms1(self): 1112 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1113 raise NotImplementedError("run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater") 1114 1115 def run_target_worker_ms1(self): 1116 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1117 raise NotImplementedError("run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater")
27class SearchMolecularFormulas: 28 """Class for searching molecular formulas in a mass spectrum. 29 30 Parameters 31 ---------- 32 mass_spectrum_obj : MassSpectrum 33 The mass spectrum object. 34 sql_db : MolForm_SQL, optional 35 The SQL database object, by default None. 36 first_hit : bool, optional 37 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 38 find_isotopologues : bool, optional 39 Flag to indicate whether to find isotopologues, by default True. 40 41 Attributes 42 ---------- 43 mass_spectrum_obj : MassSpectrum 44 The mass spectrum object. 45 sql_db : MolForm_SQL 46 The SQL database object. 47 first_hit : bool 48 Flag to indicate whether to skip peaks that already have a molecular formula assigned. 49 find_isotopologues : bool 50 Flag to indicate whether to find isotopologues. 51 52 53 Methods 54 ------- 55 * run_search(). 56 Run the molecular formula search. 57 * run_worker_mass_spectrum(). 58 Run the molecular formula search on the mass spectrum object. 59 * run_worker_ms_peaks(). 60 Run the molecular formula search on the given list of mass spectrum peaks. 61 * database_to_dict(). 62 Convert the database results to a dictionary. 63 * run_molecular_formula(). 64 Run the molecular formula search on the given list of mass spectrum peaks. 65 * search_mol_formulas(). 66 Search for molecular formulas in the mass spectrum. 67 68 """ 69 70 def __init__( 71 self, 72 mass_spectrum_obj, 73 sql_db=None, 74 first_hit: bool = False, 75 find_isotopologues: bool = True, 76 ): 77 self.first_hit = first_hit 78 79 self.find_isotopologues = find_isotopologues 80 81 self.mass_spectrum_obj = mass_spectrum_obj 82 83 if not sql_db: 84 self.sql_db = MolForm_SQL( 85 url=mass_spectrum_obj.molecular_search_settings.url_database 86 ) 87 88 else: 89 self.sql_db = sql_db 90 91 def __enter__(self): 92 """Open the SQL database connection.""" 93 return self 94 95 def __exit__(self, exc_type, exc_val, exc_tb): 96 """Close the SQL database connection.""" 97 self.sql_db.close() 98 99 return False 100 101 def run_search( 102 self, 103 mspeaks: list, 104 query: dict, 105 min_abundance: float, 106 ion_type: str, 107 ion_charge: int, 108 adduct_atom=None, 109 ): 110 """Run the molecular formula search. 111 112 Parameters 113 ---------- 114 mspeaks : list of MSPeak 115 The list of mass spectrum peaks. 116 query : dict 117 The query dictionary containing the possible molecular formulas. 118 min_abundance : float 119 The minimum abundance threshold. 120 ion_type : str 121 The ion type. 122 ion_charge : int 123 The ion charge. 124 adduct_atom : str, optional 125 The adduct atom, by default None. 126 """ 127 128 def get_formulas(nominal_overlay: float = 0.1): 129 """ 130 Get the list of formulas based on the nominal overlay. 131 132 Parameters 133 ---------- 134 nominal_overlay : float, optional 135 The nominal overlay, by default 0.1. 136 137 Returns 138 ------- 139 list 140 The list of formulas. 141 """ 142 nominal_mz = ms_peak.nominal_mz_exp 143 144 defect_mass = ms_peak.mz_exp - nominal_mz 145 nominal_masses = [nominal_mz] 146 147 if (defect_mass) >= 1 - nominal_overlay: 148 nominal_masses.append(nominal_mz + 1) 149 elif (defect_mass) <= nominal_overlay: 150 nominal_masses.append(nominal_mz - 1) 151 152 list_formulas_candidates = [] 153 154 for nominal_mass in nominal_masses: 155 if nominal_mass in query.keys(): 156 list_formulas_candidates.extend(query.get(nominal_mass)) 157 158 return list_formulas_candidates 159 160 all_assigned_indexes = list() 161 162 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 163 164 search_molfrom = SearchMolecularFormulaWorker( 165 find_isotopologues=self.find_isotopologues 166 ) 167 168 for ms_peak in mspeaks: 169 # already assigned a molecular formula 170 if self.first_hit: 171 if ms_peak.is_assigned: 172 continue 173 174 ms_peak_indexes = search_molfrom.find_formulas( 175 get_formulas(), 176 min_abundance, 177 self.mass_spectrum_obj, 178 ms_peak, 179 ion_type, 180 ion_charge, 181 adduct_atom, 182 ) 183 184 all_assigned_indexes.extend(ms_peak_indexes) 185 186 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 187 188 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 189 190 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 191 # filter per min peaks per mono isotopic class 192 193 def run_worker_mass_spectrum(self): 194 """Run the molecular formula search on the mass spectrum object.""" 195 self.run_molecular_formula( 196 self.mass_spectrum_obj.sort_by_abundance(), 197 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 198 ) 199 200 def run_worker_ms_peaks(self, ms_peaks): 201 """Run the molecular formula search on the given list of mass spectrum peaks. 202 203 Parameters 204 ---------- 205 ms_peaks : list of MSPeak 206 The list of mass spectrum peaks. 207 """ 208 self.run_molecular_formula( 209 ms_peaks, 210 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 211 ) 212 213 @staticmethod 214 def database_to_dict( 215 classe_str_list, 216 nominal_mzs, 217 mf_search_settings, 218 ion_charge, 219 sql_db=None, 220 ): 221 """Convert the database results to a dictionary. 222 223 Parameters 224 ---------- 225 classe_str_list : list 226 The list of class strings. 227 nominal_mzs : list 228 The list of nominal m/z values. 229 mf_search_settings : MolecularFormulaSearchSettings 230 The molecular formula search settings. 231 ion_charge : int 232 The ion charge. 233 sql_db : MolForm_SQL, optional 234 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 235 236 Returns 237 ------- 238 dict 239 The dictionary containing the database results. 240 """ 241 owns_db = sql_db is None 242 if owns_db: 243 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 244 245 try: 246 dict_res = {} 247 248 if mf_search_settings.isProtonated: 249 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 250 classe_str_list, 251 Labels.protonated_de_ion, 252 nominal_mzs, 253 ion_charge, 254 mf_search_settings, 255 ) 256 257 if mf_search_settings.isRadical: 258 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 259 classe_str_list, 260 Labels.radical_ion, 261 nominal_mzs, 262 ion_charge, 263 mf_search_settings, 264 ) 265 266 if mf_search_settings.isAdduct: 267 adduct_list = ( 268 mf_search_settings.adduct_atoms_neg 269 if ion_charge < 0 270 else mf_search_settings.adduct_atoms_pos 271 ) 272 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 273 classe_str_list, 274 Labels.adduct_ion, 275 nominal_mzs, 276 ion_charge, 277 mf_search_settings, 278 adducts=adduct_list, 279 ) 280 281 return dict_res 282 finally: 283 if owns_db: 284 sql_db.close() 285 286 @timeit(print_time=True) 287 def run_molecular_formula(self, ms_peaks, **kwargs): 288 """Run the molecular formula search on the given list of mass spectrum peaks. 289 290 Parameters 291 ---------- 292 ms_peaks : list of MSPeak 293 The list of mass spectrum peaks. 294 **kwargs 295 Additional keyword arguments. 296 Most notably, print_time, which is a boolean flag to indicate whether to print the time 297 and passed to the timeit decorator. 298 """ 299 ion_charge = self.mass_spectrum_obj.polarity 300 min_abundance = self.mass_spectrum_obj.min_abundance 301 nominal_mzs = self.mass_spectrum_obj.nominal_mz 302 303 verbose = self.mass_spectrum_obj.molecular_search_settings.verbose_processing 304 # reset average error, only relevant is average mass error method is being used 305 SearchMolecularFormulaWorker( 306 find_isotopologues=self.find_isotopologues 307 ).reset_error(self.mass_spectrum_obj) 308 309 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 310 classes = MolecularCombinations(self.sql_db).runworker( 311 self.mass_spectrum_obj.molecular_search_settings, 312 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 313 ) 314 315 # split the database load to not blowout the memory 316 # TODO add to the settings 317 for classe_chunk in chunks( 318 classes, self.mass_spectrum_obj.molecular_search_settings.db_chunk_size 319 ): 320 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 321 322 # load the molecular formula objs binned by ion type and heteroatoms classes, {ion type:{classe:[list_formula]}} 323 # for adduct ion type a third key is added {atoms:{ion type:{classe:[list_formula]}}} 324 dict_res = self.database_to_dict( 325 classes_str_list, 326 nominal_mzs, 327 self.mass_spectrum_obj.molecular_search_settings, 328 ion_charge, 329 sql_db=self.sql_db, 330 ) 331 pbar = tqdm.tqdm(classe_chunk, disable = not verbose) 332 for classe_tuple in pbar: 333 # class string is a json serialized dict 334 classe_str = classe_tuple[0] 335 classe_dict = classe_tuple[1] 336 337 if self.mass_spectrum_obj.molecular_search_settings.isProtonated: 338 ion_type = Labels.protonated_de_ion 339 if verbose: 340 pbar.set_description_str( 341 desc="Started molecular formula search for class %s, (de)protonated " 342 % classe_str, 343 refresh=True, 344 ) 345 346 candidate_formulas = dict_res.get(ion_type).get(classe_str) 347 348 if candidate_formulas: 349 self.run_search( 350 ms_peaks, 351 candidate_formulas, 352 min_abundance, 353 ion_type, 354 ion_charge, 355 ) 356 357 if self.mass_spectrum_obj.molecular_search_settings.isRadical: 358 if verbose: 359 pbar.set_description_str( 360 desc="Started molecular formula search for class %s, radical " 361 % classe_str, 362 refresh=True, 363 ) 364 365 ion_type = Labels.radical_ion 366 367 candidate_formulas = dict_res.get(ion_type).get(classe_str) 368 369 if candidate_formulas: 370 self.run_search( 371 ms_peaks, 372 candidate_formulas, 373 min_abundance, 374 ion_type, 375 ion_charge, 376 ) 377 # looks for adduct, used_atom_valences should be 0 378 # this code does not support H exchance by halogen atoms 379 if self.mass_spectrum_obj.molecular_search_settings.isAdduct: 380 if verbose: 381 pbar.set_description_str( 382 desc="Started molecular formula search for class %s, adduct " 383 % classe_str, 384 refresh=True, 385 ) 386 387 ion_type = Labels.adduct_ion 388 dict_atoms_formulas = dict_res.get(ion_type) 389 390 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 391 candidate_formulas = dict_by_class.get(classe_str) 392 393 if candidate_formulas: 394 self.run_search( 395 ms_peaks, 396 candidate_formulas, 397 min_abundance, 398 ion_type, 399 ion_charge, 400 adduct_atom=adduct_atom, 401 ) 402 self.sql_db.close() 403 404 def search_mol_formulas( 405 self, 406 possible_formulas_list: List[MolecularFormula], 407 ion_type: str, 408 neutral_molform=True, 409 find_isotopologues=True, 410 adduct_atom=None, 411 ) -> List[_MSPeak]: 412 """Search for molecular formulas in the mass spectrum. 413 414 Parameters 415 ---------- 416 possible_formulas_list : list of MolecularFormula 417 The list of possible molecular formulas. 418 ion_type : str 419 The ion type. 420 neutral_molform : bool, optional 421 Flag to indicate whether the molecular formulas are neutral, by default True. 422 find_isotopologues : bool, optional 423 Flag to indicate whether to find isotopologues, by default True. 424 adduct_atom : str, optional 425 The adduct atom, by default None. 426 427 Returns 428 ------- 429 list of MSPeak 430 The list of mass spectrum peaks with assigned molecular formulas. 431 """ 432 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 433 # if that is the case than turn neutral_molform off 434 435 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 436 self.mass_spectrum_obj 437 ) 438 439 initial_min_peak_bool = ( 440 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 441 ) 442 initial_runtime_kendrick_filter = ( 443 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 444 ) 445 446 # Are the following 3 lines redundant? 447 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 448 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 449 False # TODO check this line 450 ) 451 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 452 False 453 ) 454 455 possible_formulas_dict_nm = {} 456 457 for mf in possible_formulas_list: 458 if neutral_molform: 459 nm = int(mf.protonated_mz) 460 else: 461 nm = int(mf.mz_nominal_calc) 462 463 if nm in possible_formulas_dict_nm.keys(): 464 possible_formulas_dict_nm[nm].append(mf) 465 466 else: 467 possible_formulas_dict_nm[nm] = [mf] 468 469 min_abundance = self.mass_spectrum_obj.min_abundance 470 471 ion_type = ion_type 472 473 self.run_search( 474 self.mass_spectrum_obj, 475 possible_formulas_dict_nm, 476 min_abundance, 477 ion_type, 478 self.mass_spectrum_obj.polarity, 479 adduct_atom=adduct_atom, 480 ) 481 482 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 483 initial_min_peak_bool 484 ) 485 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 486 initial_runtime_kendrick_filter 487 ) 488 489 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 490 491 self.sql_db.close() 492 493 return mspeaks
Class for searching molecular formulas in a mass spectrum.
Parameters
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None.
- first_hit (bool, optional): Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Attributes
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- sql_db (MolForm_SQL): The SQL database object.
- first_hit (bool): Flag to indicate whether to skip peaks that already have a molecular formula assigned.
- find_isotopologues (bool): Flag to indicate whether to find isotopologues.
Methods
- run_search(). Run the molecular formula search.
- run_worker_mass_spectrum(). Run the molecular formula search on the mass spectrum object.
- run_worker_ms_peaks(). Run the molecular formula search on the given list of mass spectrum peaks.
- database_to_dict(). Convert the database results to a dictionary.
- run_molecular_formula(). Run the molecular formula search on the given list of mass spectrum peaks.
- search_mol_formulas(). Search for molecular formulas in the mass spectrum.
70 def __init__( 71 self, 72 mass_spectrum_obj, 73 sql_db=None, 74 first_hit: bool = False, 75 find_isotopologues: bool = True, 76 ): 77 self.first_hit = first_hit 78 79 self.find_isotopologues = find_isotopologues 80 81 self.mass_spectrum_obj = mass_spectrum_obj 82 83 if not sql_db: 84 self.sql_db = MolForm_SQL( 85 url=mass_spectrum_obj.molecular_search_settings.url_database 86 ) 87 88 else: 89 self.sql_db = sql_db
101 def run_search( 102 self, 103 mspeaks: list, 104 query: dict, 105 min_abundance: float, 106 ion_type: str, 107 ion_charge: int, 108 adduct_atom=None, 109 ): 110 """Run the molecular formula search. 111 112 Parameters 113 ---------- 114 mspeaks : list of MSPeak 115 The list of mass spectrum peaks. 116 query : dict 117 The query dictionary containing the possible molecular formulas. 118 min_abundance : float 119 The minimum abundance threshold. 120 ion_type : str 121 The ion type. 122 ion_charge : int 123 The ion charge. 124 adduct_atom : str, optional 125 The adduct atom, by default None. 126 """ 127 128 def get_formulas(nominal_overlay: float = 0.1): 129 """ 130 Get the list of formulas based on the nominal overlay. 131 132 Parameters 133 ---------- 134 nominal_overlay : float, optional 135 The nominal overlay, by default 0.1. 136 137 Returns 138 ------- 139 list 140 The list of formulas. 141 """ 142 nominal_mz = ms_peak.nominal_mz_exp 143 144 defect_mass = ms_peak.mz_exp - nominal_mz 145 nominal_masses = [nominal_mz] 146 147 if (defect_mass) >= 1 - nominal_overlay: 148 nominal_masses.append(nominal_mz + 1) 149 elif (defect_mass) <= nominal_overlay: 150 nominal_masses.append(nominal_mz - 1) 151 152 list_formulas_candidates = [] 153 154 for nominal_mass in nominal_masses: 155 if nominal_mass in query.keys(): 156 list_formulas_candidates.extend(query.get(nominal_mass)) 157 158 return list_formulas_candidates 159 160 all_assigned_indexes = list() 161 162 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 163 164 search_molfrom = SearchMolecularFormulaWorker( 165 find_isotopologues=self.find_isotopologues 166 ) 167 168 for ms_peak in mspeaks: 169 # already assigned a molecular formula 170 if self.first_hit: 171 if ms_peak.is_assigned: 172 continue 173 174 ms_peak_indexes = search_molfrom.find_formulas( 175 get_formulas(), 176 min_abundance, 177 self.mass_spectrum_obj, 178 ms_peak, 179 ion_type, 180 ion_charge, 181 adduct_atom, 182 ) 183 184 all_assigned_indexes.extend(ms_peak_indexes) 185 186 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 187 188 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 189 190 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 191 # filter per min peaks per mono isotopic class
Run the molecular formula search.
Parameters
- mspeaks (list of MSPeak): The list of mass spectrum peaks.
- query (dict): The query dictionary containing the possible molecular formulas.
- min_abundance (float): The minimum abundance threshold.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge.
- adduct_atom (str, optional): The adduct atom, by default None.
193 def run_worker_mass_spectrum(self): 194 """Run the molecular formula search on the mass spectrum object.""" 195 self.run_molecular_formula( 196 self.mass_spectrum_obj.sort_by_abundance(), 197 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 198 )
Run the molecular formula search on the mass spectrum object.
200 def run_worker_ms_peaks(self, ms_peaks): 201 """Run the molecular formula search on the given list of mass spectrum peaks. 202 203 Parameters 204 ---------- 205 ms_peaks : list of MSPeak 206 The list of mass spectrum peaks. 207 """ 208 self.run_molecular_formula( 209 ms_peaks, 210 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing 211 )
Run the molecular formula search on the given list of mass spectrum peaks.
Parameters
- ms_peaks (list of MSPeak): The list of mass spectrum peaks.
213 @staticmethod 214 def database_to_dict( 215 classe_str_list, 216 nominal_mzs, 217 mf_search_settings, 218 ion_charge, 219 sql_db=None, 220 ): 221 """Convert the database results to a dictionary. 222 223 Parameters 224 ---------- 225 classe_str_list : list 226 The list of class strings. 227 nominal_mzs : list 228 The list of nominal m/z values. 229 mf_search_settings : MolecularFormulaSearchSettings 230 The molecular formula search settings. 231 ion_charge : int 232 The ion charge. 233 sql_db : MolForm_SQL, optional 234 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 235 236 Returns 237 ------- 238 dict 239 The dictionary containing the database results. 240 """ 241 owns_db = sql_db is None 242 if owns_db: 243 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 244 245 try: 246 dict_res = {} 247 248 if mf_search_settings.isProtonated: 249 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 250 classe_str_list, 251 Labels.protonated_de_ion, 252 nominal_mzs, 253 ion_charge, 254 mf_search_settings, 255 ) 256 257 if mf_search_settings.isRadical: 258 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 259 classe_str_list, 260 Labels.radical_ion, 261 nominal_mzs, 262 ion_charge, 263 mf_search_settings, 264 ) 265 266 if mf_search_settings.isAdduct: 267 adduct_list = ( 268 mf_search_settings.adduct_atoms_neg 269 if ion_charge < 0 270 else mf_search_settings.adduct_atoms_pos 271 ) 272 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 273 classe_str_list, 274 Labels.adduct_ion, 275 nominal_mzs, 276 ion_charge, 277 mf_search_settings, 278 adducts=adduct_list, 279 ) 280 281 return dict_res 282 finally: 283 if owns_db: 284 sql_db.close()
Convert the database results to a dictionary.
Parameters
- classe_str_list (list): The list of class strings.
- nominal_mzs (list): The list of nominal m/z values.
- mf_search_settings (MolecularFormulaSearchSettings): The molecular formula search settings.
- ion_charge (int): The ion charge.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None. If None, a new MolForm_SQL object will be created.
Returns
- dict: The dictionary containing the database results.
27 def timed(*args, **kw): 28 # Extract print_time from kwargs if provided 29 local_print_time = kw.pop('print_time', print_time) 30 ts = time.time() 31 result = method(*args, **kw) 32 te = time.time() 33 if "log_time" in kw: 34 name = kw.get("log_name", method.__name__.upper()) 35 kw["log_time"][name] = int((te - ts) * 1000) 36 elif local_print_time: 37 print("%r %2.2f ms" % (method.__name__, (te - ts) * 1000)) 38 return result
The type of the None singleton.
404 def search_mol_formulas( 405 self, 406 possible_formulas_list: List[MolecularFormula], 407 ion_type: str, 408 neutral_molform=True, 409 find_isotopologues=True, 410 adduct_atom=None, 411 ) -> List[_MSPeak]: 412 """Search for molecular formulas in the mass spectrum. 413 414 Parameters 415 ---------- 416 possible_formulas_list : list of MolecularFormula 417 The list of possible molecular formulas. 418 ion_type : str 419 The ion type. 420 neutral_molform : bool, optional 421 Flag to indicate whether the molecular formulas are neutral, by default True. 422 find_isotopologues : bool, optional 423 Flag to indicate whether to find isotopologues, by default True. 424 adduct_atom : str, optional 425 The adduct atom, by default None. 426 427 Returns 428 ------- 429 list of MSPeak 430 The list of mass spectrum peaks with assigned molecular formulas. 431 """ 432 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 433 # if that is the case than turn neutral_molform off 434 435 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 436 self.mass_spectrum_obj 437 ) 438 439 initial_min_peak_bool = ( 440 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 441 ) 442 initial_runtime_kendrick_filter = ( 443 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 444 ) 445 446 # Are the following 3 lines redundant? 447 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 448 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 449 False # TODO check this line 450 ) 451 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 452 False 453 ) 454 455 possible_formulas_dict_nm = {} 456 457 for mf in possible_formulas_list: 458 if neutral_molform: 459 nm = int(mf.protonated_mz) 460 else: 461 nm = int(mf.mz_nominal_calc) 462 463 if nm in possible_formulas_dict_nm.keys(): 464 possible_formulas_dict_nm[nm].append(mf) 465 466 else: 467 possible_formulas_dict_nm[nm] = [mf] 468 469 min_abundance = self.mass_spectrum_obj.min_abundance 470 471 ion_type = ion_type 472 473 self.run_search( 474 self.mass_spectrum_obj, 475 possible_formulas_dict_nm, 476 min_abundance, 477 ion_type, 478 self.mass_spectrum_obj.polarity, 479 adduct_atom=adduct_atom, 480 ) 481 482 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 483 initial_min_peak_bool 484 ) 485 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 486 initial_runtime_kendrick_filter 487 ) 488 489 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 490 491 self.sql_db.close() 492 493 return mspeaks
Search for molecular formulas in the mass spectrum.
Parameters
- possible_formulas_list (list of MolecularFormula): The list of possible molecular formulas.
- ion_type (str): The ion type.
- neutral_molform (bool, optional): Flag to indicate whether the molecular formulas are neutral, by default True.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
- adduct_atom (str, optional): The adduct atom, by default None.
Returns
- list of MSPeak: The list of mass spectrum peaks with assigned molecular formulas.
496class SearchMolecularFormulaWorker: 497 """Class for searching molecular formulas in a mass spectrum. 498 499 Parameters 500 ---------- 501 find_isotopologues : bool, optional 502 Flag to indicate whether to find isotopologues, by default True. 503 504 Attributes 505 ---------- 506 find_isotopologues : bool 507 Flag to indicate whether to find isotopologues. 508 509 Methods 510 ------- 511 * reset_error(). 512 Reset the error variables. 513 * set_last_error(). 514 Set the last error. 515 * find_formulas(). 516 Find the formulas. 517 * calc_error(). 518 Calculate the error. 519 """ 520 521 # TODO add reset error function 522 # needs this wraper to pass the class to multiprocessing 523 524 def __init__(self, find_isotopologues=True): 525 self.find_isotopologues = find_isotopologues 526 527 def __call__(self, args): 528 """Call the find formulas function. 529 530 Parameters 531 ---------- 532 args : tuple 533 The arguments. 534 535 Returns 536 ------- 537 list 538 The list of mass spectrum peaks with assigned molecular formulas. 539 """ 540 return self.find_formulas(*args) # ,args[1] 541 542 def reset_error(self, mass_spectrum_obj): 543 """Reset the error variables. 544 545 Parameters 546 ---------- 547 mass_spectrum_obj : MassSpectrum 548 The mass spectrum object. 549 550 Notes 551 ----- 552 This function resets the error variables for the given mass spectrum object. 553 """ 554 global last_error, last_dif, closest_error, error_average, nbValues 555 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0 556 557 def set_last_error(self, error, mass_spectrum_obj): 558 """Set the last error. 559 560 Parameters 561 ---------- 562 error : float 563 The error. 564 mass_spectrum_obj : MassSpectrum 565 The mass spectrum object. 566 """ 567 # set the changes to the global variables, not internal ones 568 global last_error, last_dif, closest_error, error_average, nbValues 569 570 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 571 dif = error - last_error 572 if dif < last_dif: 573 last_dif = dif 574 closest_error = error 575 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 576 closest_error 577 - mass_spectrum_obj.molecular_search_settings.mz_error_range 578 ) 579 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 580 closest_error 581 + mass_spectrum_obj.molecular_search_settings.mz_error_range 582 ) 583 584 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 585 if error < last_error: 586 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 587 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 588 ) 589 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 590 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 591 ) 592 last_error = error 593 594 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 595 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 596 mass_spectrum_obj.molecular_search_settings.mz_error_average 597 - mass_spectrum_obj.molecular_search_settings.mz_error_range 598 ) 599 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 600 mass_spectrum_obj.molecular_search_settings.mz_error_average 601 + mass_spectrum_obj.molecular_search_settings.mz_error_range 602 ) 603 604 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 605 nbValues += 1 606 error_average = error_average + ((error - error_average) / nbValues) 607 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 608 error_average 609 - mass_spectrum_obj.molecular_search_settings.mz_error_range 610 ) 611 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 612 error_average 613 + mass_spectrum_obj.molecular_search_settings.mz_error_range 614 ) 615 616 else: 617 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 618 pass 619 620 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method 621 622 @staticmethod 623 def calc_error(mz_exp, mz_calc, method="ppm"): 624 """Calculate the error. 625 626 Parameters 627 ---------- 628 mz_exp : float 629 The experimental m/z value. 630 mz_calc : float 631 The calculated m/z value. 632 method : str, optional 633 The method, by default 'ppm'. 634 635 Raises 636 ------- 637 Exception 638 If the method is not ppm or ppb. 639 640 Returns 641 ------- 642 float 643 The error. 644 """ 645 646 if method == "ppm": 647 multi_factor = 1_000_000 648 649 elif method == "ppb": 650 multi_factor = 1_000_000_000 651 652 elif method == "perc": 653 multi_factor = 100 654 655 else: 656 raise Exception( 657 "method needs to be ppm or ppb, you have entered %s" % method 658 ) 659 660 if mz_exp: 661 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 662 663 else: 664 raise Exception("Please set mz_calc first") 665 666 def find_formulas( 667 self, 668 formulas, 669 min_abundance, 670 mass_spectrum_obj, 671 ms_peak, 672 ion_type, 673 ion_charge, 674 adduct_atom=None, 675 ): 676 """Find the formulas. 677 678 Parameters 679 ---------- 680 formulas : list of MolecularFormula 681 The list of molecular formulas. 682 min_abundance : float 683 The minimum abundance threshold. 684 mass_spectrum_obj : MassSpectrum 685 The mass spectrum object. 686 ms_peak : MSPeak 687 The mass spectrum peak. 688 ion_type : str 689 The ion type. 690 ion_charge : int 691 The ion charge. 692 adduct_atom : str, optional 693 The adduct atom, by default None. 694 695 Returns 696 ------- 697 list of MSPeak 698 The list of mass spectrum peaks with assigned molecular formulas. 699 700 Notes 701 ----- 702 Uses the closest error the next search (this is not ideal, it needs to use confidence 703 metric to choose the right candidate then propagate the error using the error from the best candidate). 704 It needs to add s/n to the equation. 705 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 706 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 707 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 708 Method='distance' 709 """ 710 mspeak_assigned_index = list() 711 712 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 713 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 714 715 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 716 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 717 718 # f = open("abundance_error.txt", "a+") 719 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 720 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 721 722 def mass_by_ion_type(possible_formula_obj): 723 if ion_type == Labels.protonated_de_ion: 724 return possible_formula_obj._protonated_mz(ion_charge) 725 726 elif ion_type == Labels.radical_ion: 727 return possible_formula_obj._radical_mz(ion_charge) 728 729 elif ion_type == Labels.adduct_ion and adduct_atom: 730 return possible_formula_obj._adduct_mz(ion_charge, adduct_atom) 731 732 else: 733 # will return externally calculated mz if is set, #use on Bruker Reference list import 734 # if the ion type is known the ion mass based on molecular formula ion type 735 # if ion type is unknow will return neutral mass 736 return possible_formula_obj.mz_calc 737 738 if formulas: 739 if isinstance(formulas[0], LCMSLibRefMolecularFormula): 740 possible_mf_class = True 741 742 else: 743 possible_mf_class = False 744 745 for possible_formula in formulas: 746 if possible_formula: 747 error = self.calc_error( 748 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 749 ) 750 751 # error = possible_formula.mz_error 752 753 if min_ppm_error <= error <= max_ppm_error: 754 # update the error 755 756 self.set_last_error(error, mass_spectrum_obj) 757 758 # add molecular formula match to ms_peak 759 760 # get molecular formula dict from sql obj 761 # formula_dict = pickle.loads(possible_formula.mol_formula) 762 # if possible_mf_class: 763 764 # molecular_formula = deepcopy(possible_formula) 765 766 # else: 767 768 formula_dict = possible_formula.to_dict() 769 # create the molecular formula obj to be stored 770 if possible_mf_class: 771 molecular_formula = LCMSLibRefMolecularFormula( 772 formula_dict, 773 ion_charge, 774 ion_type=ion_type, 775 adduct_atom=adduct_atom, 776 ) 777 778 molecular_formula.name = possible_formula.name 779 molecular_formula.kegg_id = possible_formula.kegg_id 780 molecular_formula.cas = possible_formula.cas 781 782 else: 783 molecular_formula = MolecularFormula( 784 formula_dict, 785 ion_charge, 786 ion_type=ion_type, 787 adduct_atom=adduct_atom, 788 ) 789 # add the molecular formula obj to the mspeak obj 790 # add the mspeak obj and it's index for tracking next assignment step 791 792 if self.find_isotopologues: 793 # calculates isotopologues 794 isotopologues = molecular_formula.isotopologues( 795 min_abundance, 796 ms_peak_abundance, 797 mass_spectrum_obj.dynamic_range, 798 ) 799 800 # search for isotopologues 801 for isotopologue_formula in isotopologues: 802 molecular_formula.expected_isotopologues.append( 803 isotopologue_formula 804 ) 805 # move this outside to improve preformace 806 # we need to increase the search space to -+1 m_z 807 first_index, last_index = ( 808 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 809 isotopologue_formula.mz_nominal_calc 810 ) 811 ) 812 813 for ms_peak_iso in mass_spectrum_obj[ 814 first_index:last_index 815 ]: 816 error = self.calc_error( 817 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 818 ) 819 820 if min_ppm_error <= error <= max_ppm_error: 821 # need to define error distribution for abundance measurements 822 823 # if mass_spectrum_obj.is_centroid: 824 825 abundance_error = self.calc_error( 826 isotopologue_formula.abundance_calc, 827 ms_peak_iso.abundance, 828 method="perc", 829 ) 830 831 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 832 833 # margin of error was set empirically/ needs statistical calculation 834 # of margin of error for the measurement of the abundances 835 if ( 836 min_abun_error 837 <= abundance_error 838 <= max_abun_error 839 ): 840 # update the error 841 842 self.set_last_error(error, mass_spectrum_obj) 843 844 # isotopologue_formula.mz_error = error 845 846 # isotopologue_formula.area_error = area_error 847 848 # isotopologue_formula.abundance_error = abundance_error 849 850 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 851 852 mono_isotopic_formula_index = len(ms_peak) 853 854 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 855 856 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 857 858 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 859 860 # add molecular formula match to ms_peak 861 x = ms_peak_iso.add_molecular_formula( 862 isotopologue_formula 863 ) 864 865 molecular_formula.mspeak_mf_isotopologues_indexes.append( 866 (ms_peak_iso.index, x) 867 ) 868 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 869 870 y = ms_peak.add_molecular_formula(molecular_formula) 871 872 mspeak_assigned_index.append((ms_peak.index, y)) 873 874 return mspeak_assigned_index
Class for searching molecular formulas in a mass spectrum.
Parameters
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Attributes
- find_isotopologues (bool): Flag to indicate whether to find isotopologues.
Methods
- reset_error(). Reset the error variables.
- set_last_error(). Set the last error.
- find_formulas(). Find the formulas.
- calc_error(). Calculate the error.
542 def reset_error(self, mass_spectrum_obj): 543 """Reset the error variables. 544 545 Parameters 546 ---------- 547 mass_spectrum_obj : MassSpectrum 548 The mass spectrum object. 549 550 Notes 551 ----- 552 This function resets the error variables for the given mass spectrum object. 553 """ 554 global last_error, last_dif, closest_error, error_average, nbValues 555 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0
Reset the error variables.
Parameters
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
Notes
This function resets the error variables for the given mass spectrum object.
557 def set_last_error(self, error, mass_spectrum_obj): 558 """Set the last error. 559 560 Parameters 561 ---------- 562 error : float 563 The error. 564 mass_spectrum_obj : MassSpectrum 565 The mass spectrum object. 566 """ 567 # set the changes to the global variables, not internal ones 568 global last_error, last_dif, closest_error, error_average, nbValues 569 570 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 571 dif = error - last_error 572 if dif < last_dif: 573 last_dif = dif 574 closest_error = error 575 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 576 closest_error 577 - mass_spectrum_obj.molecular_search_settings.mz_error_range 578 ) 579 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 580 closest_error 581 + mass_spectrum_obj.molecular_search_settings.mz_error_range 582 ) 583 584 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 585 if error < last_error: 586 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 587 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 588 ) 589 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 590 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 591 ) 592 last_error = error 593 594 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 595 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 596 mass_spectrum_obj.molecular_search_settings.mz_error_average 597 - mass_spectrum_obj.molecular_search_settings.mz_error_range 598 ) 599 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 600 mass_spectrum_obj.molecular_search_settings.mz_error_average 601 + mass_spectrum_obj.molecular_search_settings.mz_error_range 602 ) 603 604 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 605 nbValues += 1 606 error_average = error_average + ((error - error_average) / nbValues) 607 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 608 error_average 609 - mass_spectrum_obj.molecular_search_settings.mz_error_range 610 ) 611 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 612 error_average 613 + mass_spectrum_obj.molecular_search_settings.mz_error_range 614 ) 615 616 else: 617 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 618 pass 619 620 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method
Set the last error.
Parameters
- error (float): The error.
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
622 @staticmethod 623 def calc_error(mz_exp, mz_calc, method="ppm"): 624 """Calculate the error. 625 626 Parameters 627 ---------- 628 mz_exp : float 629 The experimental m/z value. 630 mz_calc : float 631 The calculated m/z value. 632 method : str, optional 633 The method, by default 'ppm'. 634 635 Raises 636 ------- 637 Exception 638 If the method is not ppm or ppb. 639 640 Returns 641 ------- 642 float 643 The error. 644 """ 645 646 if method == "ppm": 647 multi_factor = 1_000_000 648 649 elif method == "ppb": 650 multi_factor = 1_000_000_000 651 652 elif method == "perc": 653 multi_factor = 100 654 655 else: 656 raise Exception( 657 "method needs to be ppm or ppb, you have entered %s" % method 658 ) 659 660 if mz_exp: 661 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 662 663 else: 664 raise Exception("Please set mz_calc first")
Calculate the error.
Parameters
- mz_exp (float): The experimental m/z value.
- mz_calc (float): The calculated m/z value.
- method (str, optional): The method, by default 'ppm'.
Raises
- Exception: If the method is not ppm or ppb.
Returns
- float: The error.
666 def find_formulas( 667 self, 668 formulas, 669 min_abundance, 670 mass_spectrum_obj, 671 ms_peak, 672 ion_type, 673 ion_charge, 674 adduct_atom=None, 675 ): 676 """Find the formulas. 677 678 Parameters 679 ---------- 680 formulas : list of MolecularFormula 681 The list of molecular formulas. 682 min_abundance : float 683 The minimum abundance threshold. 684 mass_spectrum_obj : MassSpectrum 685 The mass spectrum object. 686 ms_peak : MSPeak 687 The mass spectrum peak. 688 ion_type : str 689 The ion type. 690 ion_charge : int 691 The ion charge. 692 adduct_atom : str, optional 693 The adduct atom, by default None. 694 695 Returns 696 ------- 697 list of MSPeak 698 The list of mass spectrum peaks with assigned molecular formulas. 699 700 Notes 701 ----- 702 Uses the closest error the next search (this is not ideal, it needs to use confidence 703 metric to choose the right candidate then propagate the error using the error from the best candidate). 704 It needs to add s/n to the equation. 705 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 706 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 707 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 708 Method='distance' 709 """ 710 mspeak_assigned_index = list() 711 712 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 713 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 714 715 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 716 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 717 718 # f = open("abundance_error.txt", "a+") 719 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 720 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 721 722 def mass_by_ion_type(possible_formula_obj): 723 if ion_type == Labels.protonated_de_ion: 724 return possible_formula_obj._protonated_mz(ion_charge) 725 726 elif ion_type == Labels.radical_ion: 727 return possible_formula_obj._radical_mz(ion_charge) 728 729 elif ion_type == Labels.adduct_ion and adduct_atom: 730 return possible_formula_obj._adduct_mz(ion_charge, adduct_atom) 731 732 else: 733 # will return externally calculated mz if is set, #use on Bruker Reference list import 734 # if the ion type is known the ion mass based on molecular formula ion type 735 # if ion type is unknow will return neutral mass 736 return possible_formula_obj.mz_calc 737 738 if formulas: 739 if isinstance(formulas[0], LCMSLibRefMolecularFormula): 740 possible_mf_class = True 741 742 else: 743 possible_mf_class = False 744 745 for possible_formula in formulas: 746 if possible_formula: 747 error = self.calc_error( 748 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 749 ) 750 751 # error = possible_formula.mz_error 752 753 if min_ppm_error <= error <= max_ppm_error: 754 # update the error 755 756 self.set_last_error(error, mass_spectrum_obj) 757 758 # add molecular formula match to ms_peak 759 760 # get molecular formula dict from sql obj 761 # formula_dict = pickle.loads(possible_formula.mol_formula) 762 # if possible_mf_class: 763 764 # molecular_formula = deepcopy(possible_formula) 765 766 # else: 767 768 formula_dict = possible_formula.to_dict() 769 # create the molecular formula obj to be stored 770 if possible_mf_class: 771 molecular_formula = LCMSLibRefMolecularFormula( 772 formula_dict, 773 ion_charge, 774 ion_type=ion_type, 775 adduct_atom=adduct_atom, 776 ) 777 778 molecular_formula.name = possible_formula.name 779 molecular_formula.kegg_id = possible_formula.kegg_id 780 molecular_formula.cas = possible_formula.cas 781 782 else: 783 molecular_formula = MolecularFormula( 784 formula_dict, 785 ion_charge, 786 ion_type=ion_type, 787 adduct_atom=adduct_atom, 788 ) 789 # add the molecular formula obj to the mspeak obj 790 # add the mspeak obj and it's index for tracking next assignment step 791 792 if self.find_isotopologues: 793 # calculates isotopologues 794 isotopologues = molecular_formula.isotopologues( 795 min_abundance, 796 ms_peak_abundance, 797 mass_spectrum_obj.dynamic_range, 798 ) 799 800 # search for isotopologues 801 for isotopologue_formula in isotopologues: 802 molecular_formula.expected_isotopologues.append( 803 isotopologue_formula 804 ) 805 # move this outside to improve preformace 806 # we need to increase the search space to -+1 m_z 807 first_index, last_index = ( 808 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 809 isotopologue_formula.mz_nominal_calc 810 ) 811 ) 812 813 for ms_peak_iso in mass_spectrum_obj[ 814 first_index:last_index 815 ]: 816 error = self.calc_error( 817 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 818 ) 819 820 if min_ppm_error <= error <= max_ppm_error: 821 # need to define error distribution for abundance measurements 822 823 # if mass_spectrum_obj.is_centroid: 824 825 abundance_error = self.calc_error( 826 isotopologue_formula.abundance_calc, 827 ms_peak_iso.abundance, 828 method="perc", 829 ) 830 831 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 832 833 # margin of error was set empirically/ needs statistical calculation 834 # of margin of error for the measurement of the abundances 835 if ( 836 min_abun_error 837 <= abundance_error 838 <= max_abun_error 839 ): 840 # update the error 841 842 self.set_last_error(error, mass_spectrum_obj) 843 844 # isotopologue_formula.mz_error = error 845 846 # isotopologue_formula.area_error = area_error 847 848 # isotopologue_formula.abundance_error = abundance_error 849 850 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 851 852 mono_isotopic_formula_index = len(ms_peak) 853 854 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 855 856 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 857 858 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 859 860 # add molecular formula match to ms_peak 861 x = ms_peak_iso.add_molecular_formula( 862 isotopologue_formula 863 ) 864 865 molecular_formula.mspeak_mf_isotopologues_indexes.append( 866 (ms_peak_iso.index, x) 867 ) 868 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 869 870 y = ms_peak.add_molecular_formula(molecular_formula) 871 872 mspeak_assigned_index.append((ms_peak.index, y)) 873 874 return mspeak_assigned_index
Find the formulas.
Parameters
- formulas (list of MolecularFormula): The list of molecular formulas.
- min_abundance (float): The minimum abundance threshold.
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- ms_peak (MSPeak): The mass spectrum peak.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge.
- adduct_atom (str, optional): The adduct atom, by default None.
Returns
- list of MSPeak: The list of mass spectrum peaks with assigned molecular formulas.
Notes
Uses the closest error the next search (this is not ideal, it needs to use confidence metric to choose the right candidate then propagate the error using the error from the best candidate). It needs to add s/n to the equation. It need optimization to define the mz_error_range within a m/z unit since it is directly proportional with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. Method='distance'
877class SearchMolecularFormulasLC: 878 """Class for searching molecular formulas in a LC object. 879 880 Parameters 881 ---------- 882 lcms_obj : LCMSBase 883 The LCMSBase object. 884 sql_db : MolForm_SQL, optional 885 The SQL database object, by default None. 886 first_hit : bool, optional 887 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 888 find_isotopologues : bool, optional 889 Flag to indicate whether to find isotopologues, by default True. 890 891 Methods 892 ------- 893 894 * search_spectra_against_candidates(). 895 Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 896 * bulk_run_molecular_formula_search(). 897 Run the molecular formula search on the given list of mass spectra. 898 Pulls the settings from the LCMSBase object to set ion type and charge to search for. 899 * run_mass_feature_search(). 900 Run the molecular formula search on mass features. 901 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 902 * run_untargeted_worker_ms1(). 903 Run untargeted molecular formula search on the ms1 mass spectrum. 904 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 905 * run_target_worker_ms1(). 906 Run targeted molecular formula search on the ms1 mass spectrum. 907 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 908 """ 909 910 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 911 self.first_hit = first_hit 912 913 self.find_isotopologues = find_isotopologues 914 915 self.lcms_obj = lcms_obj 916 917 if not sql_db: 918 self.sql_db = MolForm_SQL( 919 url=self.lcms_obj.parameters.mass_spectrum['ms1'].molecular_search.url_database 920 ) 921 922 else: 923 self.sql_db = sql_db 924 925 def search_spectra_against_candidates(self, mass_spectrum_list, ms_peaks_list, candidate_formulas, ion_type, ion_charge): 926 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 927 928 Parameters 929 ---------- 930 mass_spectrum_list : list of MassSpectrum 931 The list of mass spectra to perform the search on. 932 ms_peaks_list : list of lists of MSPeak objects 933 The list of mass spectrum peaks to search within each mass spectrum. 934 candidate_formulas : dict 935 The candidate formulas. 936 ion_type : str 937 The ion type. 938 ion_charge : int 939 The ion charge, either 1 or -1. 940 941 Notes 942 ----- 943 This function is designed to be used with the bulk_run_molecular_formula_search function. 944 """ 945 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 946 single_ms_search = SearchMolecularFormulas( 947 mass_spectrum, 948 sql_db=self.sql_db, 949 first_hit=self.first_hit, 950 find_isotopologues=self.find_isotopologues, 951 ) 952 single_ms_search.run_search( 953 ms_peaks, 954 candidate_formulas, 955 mass_spectrum.min_abundance, 956 ion_type, 957 ion_charge, 958 ) 959 960 def bulk_run_molecular_formula_search(self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key='ms1'): 961 """Run the molecular formula search on the given list of mass spectra 962 963 Parameters 964 ---------- 965 mass_spectrum_list : list of MassSpectrum 966 The list of mass spectra to search. 967 ms_peaks_list : list of lists of MSPeak objects 968 The mass peaks to perform molecular formula search within each mass spectrum 969 mass_spectrum_setting_key : str, optional 970 The mass spectrum setting key, by default 'ms1'. 971 This is used to get the appropriate molecular search settings from the LCMSBase object 972 """ 973 # Set min_abundance and nominal_mzs 974 if self.lcms_obj.polarity == "positive": 975 ion_charge = 1 976 elif self.lcms_obj.polarity == "negative": 977 ion_charge = -1 978 else: 979 raise ValueError("Polarity must be either 'positive' or 'negative'") 980 981 # Check that the length of the mass spectrum list and the ms_peaks list are the same 982 if len(mass_spectrum_list) != len(ms_peaks_list): 983 raise ValueError("The length of the mass spectrum list and the ms_peaks list must be the same") 984 985 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 986 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 987 verbose = self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 988 989 # reset average error, only relevant if average mass error method is being used 990 SearchMolecularFormulaWorker( 991 find_isotopologues=self.find_isotopologues 992 ).reset_error(mass_spectrum_list[0]) 993 994 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 995 classes = MolecularCombinations(self.sql_db).runworker( 996 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 997 print_time=self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 998 ) 999 1000 try: 1001 # split the database load to not blowout the memory 1002 for classe_chunk in chunks( 1003 classes, self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.db_chunk_size 1004 ): 1005 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 1006 1007 # load the molecular formula objs binned by ion type and heteroatoms classes, {ion type:{classe:[list_formula]}} 1008 # for adduct ion type a third key is added {atoms:{ion type:{classe:[list_formula]}}} 1009 dict_res = SearchMolecularFormulas.database_to_dict( 1010 classes_str_list, 1011 nominal_mzs, 1012 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 1013 ion_charge, 1014 sql_db=self.sql_db, 1015 ) 1016 1017 pbar = tqdm.tqdm(classe_chunk, disable=not verbose) 1018 for classe_tuple in pbar: 1019 # class string is a json serialized dict 1020 classe_str = classe_tuple[0] 1021 1022 # Perform search for (de)protonated ion type 1023 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isProtonated: 1024 ion_type = Labels.protonated_de_ion 1025 1026 pbar.set_description_str( 1027 desc="Started molecular formula search for class %s, (de)protonated " 1028 % classe_str, 1029 refresh=True, 1030 ) 1031 1032 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1033 1034 if candidate_formulas: 1035 self.search_spectra_against_candidates( 1036 mass_spectrum_list=mass_spectrum_list, 1037 ms_peaks_list=ms_peaks_list, 1038 candidate_formulas=candidate_formulas, 1039 ion_type=ion_type, 1040 ion_charge=ion_charge, 1041 ) 1042 1043 # Perform search for radical ion type 1044 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isRadical: 1045 pbar.set_description_str( 1046 desc="Started molecular formula search for class %s, radical " 1047 % classe_str, 1048 refresh=True, 1049 ) 1050 1051 ion_type = Labels.radical_ion 1052 1053 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1054 1055 if candidate_formulas: 1056 self.search_spectra_against_candidates( 1057 mass_spectrum_list=mass_spectrum_list, 1058 ms_peaks_list=ms_peaks_list, 1059 candidate_formulas=candidate_formulas, 1060 ion_type=ion_type, 1061 ion_charge=ion_charge, 1062 ) 1063 1064 # Perform search for adduct ion type 1065 # looks for adduct, used_atom_valences should be 0 1066 # this code does not support H exchance by halogen atoms 1067 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isAdduct: 1068 pbar.set_description_str( 1069 desc="Started molecular formula search for class %s, adduct " 1070 % classe_str, 1071 refresh=True, 1072 ) 1073 1074 ion_type = Labels.adduct_ion 1075 dict_atoms_formulas = dict_res.get(ion_type) 1076 1077 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 1078 candidate_formulas = dict_by_class.get(classe_str) 1079 1080 if candidate_formulas: 1081 self.search_spectra_against_candidates( 1082 mass_spectrum_list=mass_spectrum_list, 1083 ms_peaks_list=ms_peaks_list, 1084 candidate_formulas=candidate_formulas, 1085 ion_type=ion_type, 1086 ion_charge=ion_charge, 1087 ) 1088 finally: 1089 self.sql_db.close() 1090 1091 def run_mass_feature_search(self): 1092 """Run the molecular formula search on the mass features. 1093 1094 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1095 """ 1096 mass_features_df = self.lcms_obj.mass_features_to_df() 1097 1098 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1099 scan_list = mass_features_df.apex_scan.unique() 1100 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1101 ms_peaks = [] 1102 for scan in scan_list: 1103 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1104 peaks_to_search = [ 1105 self.lcms_obj.mass_features[x].ms1_peak for x in mf_df_scan.index.tolist() 1106 ] 1107 ms_peaks.append(peaks_to_search) 1108 1109 # Run the molecular formula search 1110 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks) 1111 1112 def run_untargeted_worker_ms1(self): 1113 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1114 raise NotImplementedError("run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater") 1115 1116 def run_target_worker_ms1(self): 1117 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1118 raise NotImplementedError("run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater")
Class for searching molecular formulas in a LC object.
Parameters
- lcms_obj (LCMSBase): The LCMSBase object.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None.
- first_hit (bool, optional): Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Methods
- search_spectra_against_candidates(). Search a list of mass spectra against a list of candidate formulas with a given ion type and charge.
- bulk_run_molecular_formula_search(). Run the molecular formula search on the given list of mass spectra. Pulls the settings from the LCMSBase object to set ion type and charge to search for.
- run_mass_feature_search(). Run the molecular formula search on mass features. Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks.
- run_untargeted_worker_ms1(). Run untargeted molecular formula search on the ms1 mass spectrum. DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead.
- run_target_worker_ms1(). Run targeted molecular formula search on the ms1 mass spectrum. DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead.
910 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 911 self.first_hit = first_hit 912 913 self.find_isotopologues = find_isotopologues 914 915 self.lcms_obj = lcms_obj 916 917 if not sql_db: 918 self.sql_db = MolForm_SQL( 919 url=self.lcms_obj.parameters.mass_spectrum['ms1'].molecular_search.url_database 920 ) 921 922 else: 923 self.sql_db = sql_db
925 def search_spectra_against_candidates(self, mass_spectrum_list, ms_peaks_list, candidate_formulas, ion_type, ion_charge): 926 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 927 928 Parameters 929 ---------- 930 mass_spectrum_list : list of MassSpectrum 931 The list of mass spectra to perform the search on. 932 ms_peaks_list : list of lists of MSPeak objects 933 The list of mass spectrum peaks to search within each mass spectrum. 934 candidate_formulas : dict 935 The candidate formulas. 936 ion_type : str 937 The ion type. 938 ion_charge : int 939 The ion charge, either 1 or -1. 940 941 Notes 942 ----- 943 This function is designed to be used with the bulk_run_molecular_formula_search function. 944 """ 945 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 946 single_ms_search = SearchMolecularFormulas( 947 mass_spectrum, 948 sql_db=self.sql_db, 949 first_hit=self.first_hit, 950 find_isotopologues=self.find_isotopologues, 951 ) 952 single_ms_search.run_search( 953 ms_peaks, 954 candidate_formulas, 955 mass_spectrum.min_abundance, 956 ion_type, 957 ion_charge, 958 )
Search a list of mass spectra against a list of candidate formulas with a given ion type and charge.
Parameters
- mass_spectrum_list (list of MassSpectrum): The list of mass spectra to perform the search on.
- ms_peaks_list (list of lists of MSPeak objects): The list of mass spectrum peaks to search within each mass spectrum.
- candidate_formulas (dict): The candidate formulas.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge, either 1 or -1.
Notes
This function is designed to be used with the bulk_run_molecular_formula_search function.
960 def bulk_run_molecular_formula_search(self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key='ms1'): 961 """Run the molecular formula search on the given list of mass spectra 962 963 Parameters 964 ---------- 965 mass_spectrum_list : list of MassSpectrum 966 The list of mass spectra to search. 967 ms_peaks_list : list of lists of MSPeak objects 968 The mass peaks to perform molecular formula search within each mass spectrum 969 mass_spectrum_setting_key : str, optional 970 The mass spectrum setting key, by default 'ms1'. 971 This is used to get the appropriate molecular search settings from the LCMSBase object 972 """ 973 # Set min_abundance and nominal_mzs 974 if self.lcms_obj.polarity == "positive": 975 ion_charge = 1 976 elif self.lcms_obj.polarity == "negative": 977 ion_charge = -1 978 else: 979 raise ValueError("Polarity must be either 'positive' or 'negative'") 980 981 # Check that the length of the mass spectrum list and the ms_peaks list are the same 982 if len(mass_spectrum_list) != len(ms_peaks_list): 983 raise ValueError("The length of the mass spectrum list and the ms_peaks list must be the same") 984 985 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 986 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 987 verbose = self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 988 989 # reset average error, only relevant if average mass error method is being used 990 SearchMolecularFormulaWorker( 991 find_isotopologues=self.find_isotopologues 992 ).reset_error(mass_spectrum_list[0]) 993 994 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 995 classes = MolecularCombinations(self.sql_db).runworker( 996 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 997 print_time=self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.verbose_processing 998 ) 999 1000 try: 1001 # split the database load to not blowout the memory 1002 for classe_chunk in chunks( 1003 classes, self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.db_chunk_size 1004 ): 1005 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 1006 1007 # load the molecular formula objs binned by ion type and heteroatoms classes, {ion type:{classe:[list_formula]}} 1008 # for adduct ion type a third key is added {atoms:{ion type:{classe:[list_formula]}}} 1009 dict_res = SearchMolecularFormulas.database_to_dict( 1010 classes_str_list, 1011 nominal_mzs, 1012 self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search, 1013 ion_charge, 1014 sql_db=self.sql_db, 1015 ) 1016 1017 pbar = tqdm.tqdm(classe_chunk, disable=not verbose) 1018 for classe_tuple in pbar: 1019 # class string is a json serialized dict 1020 classe_str = classe_tuple[0] 1021 1022 # Perform search for (de)protonated ion type 1023 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isProtonated: 1024 ion_type = Labels.protonated_de_ion 1025 1026 pbar.set_description_str( 1027 desc="Started molecular formula search for class %s, (de)protonated " 1028 % classe_str, 1029 refresh=True, 1030 ) 1031 1032 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1033 1034 if candidate_formulas: 1035 self.search_spectra_against_candidates( 1036 mass_spectrum_list=mass_spectrum_list, 1037 ms_peaks_list=ms_peaks_list, 1038 candidate_formulas=candidate_formulas, 1039 ion_type=ion_type, 1040 ion_charge=ion_charge, 1041 ) 1042 1043 # Perform search for radical ion type 1044 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isRadical: 1045 pbar.set_description_str( 1046 desc="Started molecular formula search for class %s, radical " 1047 % classe_str, 1048 refresh=True, 1049 ) 1050 1051 ion_type = Labels.radical_ion 1052 1053 candidate_formulas = dict_res.get(ion_type).get(classe_str) 1054 1055 if candidate_formulas: 1056 self.search_spectra_against_candidates( 1057 mass_spectrum_list=mass_spectrum_list, 1058 ms_peaks_list=ms_peaks_list, 1059 candidate_formulas=candidate_formulas, 1060 ion_type=ion_type, 1061 ion_charge=ion_charge, 1062 ) 1063 1064 # Perform search for adduct ion type 1065 # looks for adduct, used_atom_valences should be 0 1066 # this code does not support H exchance by halogen atoms 1067 if self.lcms_obj.parameters.mass_spectrum[mass_spectrum_setting_key].molecular_search.isAdduct: 1068 pbar.set_description_str( 1069 desc="Started molecular formula search for class %s, adduct " 1070 % classe_str, 1071 refresh=True, 1072 ) 1073 1074 ion_type = Labels.adduct_ion 1075 dict_atoms_formulas = dict_res.get(ion_type) 1076 1077 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 1078 candidate_formulas = dict_by_class.get(classe_str) 1079 1080 if candidate_formulas: 1081 self.search_spectra_against_candidates( 1082 mass_spectrum_list=mass_spectrum_list, 1083 ms_peaks_list=ms_peaks_list, 1084 candidate_formulas=candidate_formulas, 1085 ion_type=ion_type, 1086 ion_charge=ion_charge, 1087 ) 1088 finally: 1089 self.sql_db.close()
Run the molecular formula search on the given list of mass spectra
Parameters
- mass_spectrum_list (list of MassSpectrum): The list of mass spectra to search.
- ms_peaks_list (list of lists of MSPeak objects): The mass peaks to perform molecular formula search within each mass spectrum
- mass_spectrum_setting_key (str, optional): The mass spectrum setting key, by default 'ms1'. This is used to get the appropriate molecular search settings from the LCMSBase object
1091 def run_mass_feature_search(self): 1092 """Run the molecular formula search on the mass features. 1093 1094 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1095 """ 1096 mass_features_df = self.lcms_obj.mass_features_to_df() 1097 1098 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1099 scan_list = mass_features_df.apex_scan.unique() 1100 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1101 ms_peaks = [] 1102 for scan in scan_list: 1103 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1104 peaks_to_search = [ 1105 self.lcms_obj.mass_features[x].ms1_peak for x in mf_df_scan.index.tolist() 1106 ] 1107 ms_peaks.append(peaks_to_search) 1108 1109 # Run the molecular formula search 1110 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks)
Run the molecular formula search on the mass features.
Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks.
1112 def run_untargeted_worker_ms1(self): 1113 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1114 raise NotImplementedError("run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater")
Run untargeted molecular formula search on the ms1 mass spectrum.
1116 def run_target_worker_ms1(self): 1117 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1118 raise NotImplementedError("run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater")
Run targeted molecular formula search on the ms1 mass spectrum.