corems.mass_spectrum.output.export
1__author__ = "Yuri E. Corilo" 2__date__ = "Nov 11, 2019" 3 4import json 5from datetime import datetime, timezone 6from pathlib import Path 7from threading import Thread 8 9import h5py 10import numpy as np 11from numpy import nan as NaN 12from pandas import DataFrame 13 14# Labels is referenced only inside eval() below. Ruff cannot see that use. 15from corems.encapsulation.constant import ( 16 Atoms, 17 DEFAULT_HDF5_COMPRESSION, 18 Labels, # noqa: F401 19) 20from corems.encapsulation.output import parameter_to_dict 21from corems.encapsulation.output.parameter_to_json import _dumps_toml 22from corems.mass_spectrum.factory.MassSpectrumClasses import MassSpecfromFreq 23 24 25def hdf5_dataset_kwargs(shape, compression=DEFAULT_HDF5_COMPRESSION, vlen_str=False): 26 """Build ``h5py.Group.create_dataset`` filter kwargs for a compression choice. 27 28 Centralizes the HDF5 writers' compression choice (one helper used by every 29 ``create_dataset`` call on the mass spectrum / LC-MS export path) and the 30 filter exceptions h5py enforces: it refuses chunk/filter options on scalar 31 (``shape == ()``) or zero-length datasets, and the fletcher32 checksum 32 filter gains nothing (and h5py can reject it) on variable-length string 33 datasets. 34 35 Parameters 36 ---------- 37 shape : tuple 38 Shape of the data about to be written. 39 compression : int, str or None, optional 40 A gzip level 0-9, ``"lzf"``, or ``None``/``"none"`` for no 41 compression. Default is 4. ``"lzf"`` is h5py's own filter, not part 42 of core HDF5 -- a file written with it needs h5py (or another tool 43 with the lzf plugin available) to read the compressed datasets 44 back; gzip is the portable choice across other HDF5 tools. 45 vlen_str : bool, optional 46 True when the dataset's dtype is a variable-length string 47 (``h5py.string_dtype()``); skips the shuffle/fletcher32 filters that 48 don't apply to vlen data. Default is False. 49 50 Returns 51 ------- 52 dict 53 Keyword arguments to splat into ``create_dataset`` (besides ``data`` 54 and ``dtype``). 55 """ 56 if not shape or 0 in shape: 57 return {} 58 59 kwargs = {"chunks": True} 60 61 if compression not in (None, "none"): 62 if compression == "lzf": 63 kwargs["compression"] = "lzf" 64 else: 65 kwargs["compression"] = "gzip" 66 kwargs["compression_opts"] = int(compression) 67 68 if not vlen_str: 69 kwargs["shuffle"] = True 70 kwargs["fletcher32"] = True 71 72 return kwargs 73 74 75class HighResMassSpecExport(Thread): 76 """A class for exporting high-resolution mass spectra. 77 78 Parameters 79 ---------- 80 out_file_path : str 81 The output file path. 82 mass_spectrum : MassSpectrum 83 The mass spectrum to export. 84 output_type : str, optional 85 The type of output file. Defaults to 'excel'. Can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'. 86 87 Attributes 88 ---------- 89 output_file : Path 90 The output file path. 91 output_type : str 92 The type of output file. 93 mass_spectrum : MassSpectrum 94 The mass spectrum to export. 95 atoms_order_list : list 96 The list of assigned atoms in the order specified by Atoms.atoms_order list. 97 columns_label : list 98 The column labels in order. 99 100 Methods 101 ------- 102 * save(). 103 Save the mass spectrum data to the output file. 104 * run(). 105 Run the export process. 106 * get_pandas_df(). 107 Returns the mass spectrum data as a pandas DataFrame. 108 * write_settings(output_path, mass_spectrum). 109 Writes the settings of the mass spectrum to a JSON file. 110 * to_pandas(write_metadata=True). 111 Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file. 112 * to_excel(write_metadata=True). 113 Exports the mass spectrum data to an Excel file. 114 * to_csv(write_metadata=True). 115 Exports the mass spectrum data to a CSV file. 116 * to_parquet(write_metadata=True). 117 Exports the mass spectrum data to a Parquet file. 118 * to_json(). 119 Exports the mass spectrum data to a JSON string. 120 * to_hdf(). 121 Exports the mass spectrum data to an HDF5 file. 122 * parameters_to_toml(). 123 Converts the mass spectrum parameters to a TOML string. 124 * parameters_to_json(). 125 Converts the mass spectrum parameters to a JSON string. 126 * get_mass_spec_attrs(mass_spectrum). 127 Returns the mass spectrum attributes as a dictionary. 128 * get_all_used_atoms_in_order(mass_spectrum). 129 Returns the list of assigned atoms in the order specified by Atoms.atoms_order list. 130 * list_dict_to_list(mass_spectrum, is_hdf5=False). 131 Returns the mass spectrum data as a list of dictionaries. 132 * get_list_dict_data(mass_spectrum, include_no_match=True, include_isotopologues=True, isotopologue_inline=True, no_match_inline=False, is_hdf5=False). 133 Returns the mass spectrum data as a list of dictionaries. 134 135 """ 136 137 def __init__(self, out_file_path, mass_spectrum, output_type="excel"): 138 Thread.__init__(self) 139 140 self.output_file = Path(out_file_path) 141 142 # 'excel', 'csv' or 'pandas' 143 self.output_type = output_type 144 145 self.mass_spectrum = mass_spectrum 146 147 # collect all assigned atoms and order them accordingly to the Atoms.atoms_order list 148 self.atoms_order_list = self.get_all_used_atoms_in_order(self.mass_spectrum) 149 150 self._init_columns() 151 152 def _init_columns(self): 153 """Initialize the columns for the mass spectrum output.""" 154 # column labels in order 155 self.columns_label = [ 156 "Index", 157 "m/z", 158 "Calibrated m/z", 159 "Calculated m/z", 160 "Peak Height", 161 "Peak Area", 162 "Resolving Power", 163 "S/N", 164 "Ion Charge", 165 "m/z Error (ppm)", 166 "m/z Error Score", 167 "Isotopologue Similarity", 168 "Confidence Score", 169 "DBE", 170 "O/C", 171 "H/C", 172 "Heteroatom Class", 173 "Ion Type", 174 "Adduct", 175 "Is Isotopologue", 176 "Mono Isotopic Index", 177 "Molecular Formula", 178 ] 179 180 @property 181 def output_type(self): 182 """Returns the output type of the mass spectrum.""" 183 return self._output_type 184 185 @output_type.setter 186 def output_type(self, output_type): 187 output_types = ["excel", "csv", "pandas", "parquet", "hdf5"] 188 if output_type in output_types: 189 self._output_type = output_type 190 else: 191 raise TypeError( 192 'Supported types are "excel", "csv", "pandas", "parquet" or "hdf5", %s entered' 193 % output_type 194 ) 195 196 def save(self): 197 """Save the mass spectrum data to the output file. 198 199 Raises 200 ------ 201 ValueError 202 If the output type is not supported. 203 """ 204 205 if self.output_type == "excel": 206 self.to_excel() 207 elif self.output_type == "csv": 208 self.to_csv() 209 elif self.output_type == "pandas": 210 self.to_pandas() 211 elif self.output_type == "parquet": 212 self.to_parquet() 213 elif self.output_type == "hdf5": 214 self.to_hdf() 215 else: 216 raise ValueError( 217 "Unkown output type: %s; it can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'" 218 % self.output_type 219 ) 220 221 def run(self): 222 """Run the export process. 223 224 This method is called when the thread starts. 225 It calls the save method to perform the export.""" 226 self.save() 227 228 def get_pandas_df(self, additional_columns=None): 229 """Returns the mass spectrum data as a pandas DataFrame. 230 231 Parameters 232 ---------- 233 additional_columns : list, optional 234 Additional columns to include in the DataFrame. Defaults to None. 235 Suitable additional columns are: 'Aromaticity Index', 'NOSC', 'Aromaticity Index (modified)'. 236 237 Returns 238 ------- 239 DataFrame 240 The mass spectrum data as a pandas DataFrame. 241 """ 242 if additional_columns is not None: 243 possible_additional_columns = [ 244 "Aromaticity Index", 245 "NOSC", 246 "Aromaticity Index (modified)", 247 ] 248 if additional_columns: 249 for column in additional_columns: 250 if column not in possible_additional_columns: 251 raise ValueError("Invalid additional column: %s" % column) 252 columns = ( 253 self.columns_label 254 + additional_columns 255 + self.get_all_used_atoms_in_order(self.mass_spectrum) 256 ) 257 else: 258 columns = self.columns_label + self.get_all_used_atoms_in_order( 259 self.mass_spectrum 260 ) 261 dict_data_list = self.get_list_dict_data( 262 self.mass_spectrum, additional_columns=additional_columns 263 ) 264 df = DataFrame(dict_data_list, columns=columns) 265 df.attrs["name"] = self.output_file 266 return df 267 268 def write_settings(self, output_path, mass_spectrum): 269 """Writes the settings of the mass spectrum to a JSON file. 270 271 Parameters 272 ---------- 273 output_path : str 274 The output file path. 275 mass_spectrum : MassSpectrum 276 The mass spectrum to export. 277 """ 278 279 import json 280 281 dict_setting = parameter_to_dict.get_dict_data_ms(mass_spectrum) 282 283 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(mass_spectrum) 284 dict_setting["analyzer"] = mass_spectrum.analyzer 285 dict_setting["instrument_label"] = mass_spectrum.instrument_label 286 dict_setting["sample_name"] = mass_spectrum.sample_name 287 288 with open( 289 output_path.with_suffix(".json"), 290 "w", 291 encoding="utf8", 292 ) as outfile: 293 output = json.dumps( 294 dict_setting, sort_keys=True, indent=4, separators=(",", ": ") 295 ) 296 outfile.write(output) 297 298 def to_pandas(self, write_metadata=True): 299 """Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file. 300 301 Parameters 302 ---------- 303 write_metadata : bool, optional 304 Whether to write the metadata to a JSON file. Defaults to True. 305 """ 306 307 columns = self.columns_label + self.get_all_used_atoms_in_order( 308 self.mass_spectrum 309 ) 310 311 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 312 313 df = DataFrame(dict_data_list, columns=columns) 314 315 df.to_pickle(self.output_file.with_suffix(".pkl")) 316 317 if write_metadata: 318 self.write_settings(self.output_file, self.mass_spectrum) 319 320 def to_excel(self, write_metadata=True): 321 """Exports the mass spectrum data to an Excel file. 322 323 Parameters 324 ---------- 325 write_metadata : bool, optional 326 Whether to write the metadata to a JSON file. Defaults to True. 327 """ 328 329 columns = self.columns_label + self.get_all_used_atoms_in_order( 330 self.mass_spectrum 331 ) 332 333 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 334 335 df = DataFrame(dict_data_list, columns=columns) 336 337 df.to_excel(self.output_file.with_suffix(".xlsx")) 338 339 if write_metadata: 340 self.write_settings(self.output_file, self.mass_spectrum) 341 342 def to_csv(self, write_metadata=True): 343 """Exports the mass spectrum data to a CSV file. 344 345 Parameters 346 ---------- 347 write_metadata : bool, optional 348 Whether to write the metadata to a JSON file. Defaults to True. 349 """ 350 351 columns = self.columns_label + self.get_all_used_atoms_in_order( 352 self.mass_spectrum 353 ) 354 355 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 356 357 import csv 358 359 try: 360 with open(self.output_file.with_suffix(".csv"), "w", newline="") as csvfile: 361 writer = csv.DictWriter(csvfile, fieldnames=columns) 362 writer.writeheader() 363 for data in dict_data_list: 364 writer.writerow(data) 365 if write_metadata: 366 self.write_settings(self.output_file, self.mass_spectrum) 367 368 except IOError as ioerror: 369 print(ioerror) 370 371 def to_parquet(self, write_metadata=True): 372 """Exports the mass spectrum data to a Parquet file. 373 374 Parameters 375 ---------- 376 write_metadata : bool, optional 377 Whether to write the metadata to a JSON file. Defaults to True. 378 379 Notes 380 ----- 381 Requires ``pyarrow`` (or ``fastparquet``) for pandas Parquet I/O. 382 """ 383 384 columns = self.columns_label + self.get_all_used_atoms_in_order( 385 self.mass_spectrum 386 ) 387 388 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 389 390 df = DataFrame(dict_data_list, columns=columns) 391 392 df.to_parquet(self.output_file.with_suffix(".parquet"), index=False) 393 394 if write_metadata: 395 self.write_settings(self.output_file, self.mass_spectrum) 396 397 def to_json(self): 398 """Exports the mass spectrum data to a JSON string.""" 399 400 columns = self.columns_label + self.get_all_used_atoms_in_order( 401 self.mass_spectrum 402 ) 403 404 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 405 406 df = DataFrame(dict_data_list, columns=columns) 407 408 # for key, values in dict_data.items(): 409 # if not values: dict_data[key] = NaN 410 411 # output = json.dumps(dict_data, sort_keys=True, indent=4, separators=(',', ': ')) 412 return df.to_json(orient="records") 413 414 def add_mass_spectrum_to_hdf5( 415 self, 416 hdf_handle, 417 mass_spectrum, 418 group_key, 419 mass_spectra_group=None, 420 export_raw=True, 421 compression=DEFAULT_HDF5_COMPRESSION, 422 profile_dtype="f4", 423 ): 424 """Adds the mass spectrum data to an HDF5 file. 425 426 Parameters 427 ---------- 428 hdf_handle : h5py.File 429 The HDF5 file handle. 430 mass_spectrum : MassSpectrum 431 The mass spectrum to add to the HDF5 file. 432 group_key : str 433 The group key (where to add the mass spectrum data within the HDF5 file). 434 mass_spectra_group : h5py.Group, optional 435 The mass spectra group. Defaults to None (no group, mass spectrum is added to the root). 436 export_raw : bool, optional 437 Whether to export the raw data. Defaults to True. 438 If False, only the processed data (peaks) is exported (essentially centroided data). 439 compression : int, str or None, optional 440 Compression for the written datasets: a gzip level 0-9, ``"lzf"``, 441 or ``None``/``"none"``. Default is 4. ``"lzf"`` is h5py's own 442 filter, not a standard HDF5 one -- it needs h5py (or a tool 443 with the lzf plugin) to read the result back. 444 profile_dtype : str, optional 445 dtype used for the stored profile m/z/abundance arrays 446 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 447 """ 448 if mass_spectra_group is None: 449 # Check if the file has the necessary attributes and add them if not 450 # This assumes that if there is a mass_spectra_group, these attributes were already added to the file 451 if not hdf_handle.attrs.get("date_utc"): 452 timenow = str( 453 datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z") 454 ) 455 hdf_handle.attrs["date_utc"] = timenow 456 hdf_handle.attrs["file_name"] = mass_spectrum.filename.name 457 hdf_handle.attrs["data_structure"] = "mass_spectrum" 458 hdf_handle.attrs["analyzer"] = mass_spectrum.analyzer 459 hdf_handle.attrs["instrument_label"] = mass_spectrum.instrument_label 460 hdf_handle.attrs["sample_name"] = mass_spectrum.sample_name 461 462 list_results = self.list_dict_to_list(mass_spectrum, is_hdf5=True) 463 464 dict_ms_attrs = self.get_mass_spec_attrs(mass_spectrum) 465 466 setting_dicts = parameter_to_dict.get_dict_data_ms(mass_spectrum) 467 468 columns_labels = json.dumps( 469 self.columns_label + self.get_all_used_atoms_in_order(mass_spectrum), 470 sort_keys=False, 471 indent=4, 472 separators=(",", ": "), 473 ) 474 475 group_key = group_key 476 477 if mass_spectra_group is not None: 478 hdf_handle = mass_spectra_group 479 480 if group_key not in hdf_handle.keys(): 481 scan_group = hdf_handle.create_group(group_key) 482 483 # If there is raw data (from profile data) save it 484 if not mass_spectrum.is_centroid and export_raw: 485 # "mz" is always the experimental (uncalibrated) profile; a 486 # separate "mz_cal" row is added only when the spectrum is 487 # actually calibrated. mz_cal_profile alone isn't enough to 488 # tell: MzDomainCalibration.__init__ pre-populates it before 489 # any calibration has run, so a never-calibrated spectrum 490 # can still have a (stale/placeholder) mz_cal_profile. 491 row_labels = ["mz", "abundance"] 492 rows = [ 493 np.asarray(mass_spectrum._mz_exp), 494 np.asarray(mass_spectrum.abundance_profile), 495 ] 496 mz_cal_profile = mass_spectrum.mz_cal_profile 497 if mass_spectrum.is_calibrated and mz_cal_profile is not None: 498 row_labels.append("mz_cal") 499 rows.append(np.asarray(mz_cal_profile)) 500 501 mz_abun_array = np.array(rows, dtype=profile_dtype) 502 503 raw_ms_dataset = scan_group.create_dataset( 504 "raw_ms", 505 data=mz_abun_array, 506 dtype=profile_dtype, 507 **hdf5_dataset_kwargs(mz_abun_array.shape, compression=compression), 508 ) 509 raw_ms_dataset.attrs["row_labels"] = json.dumps(row_labels) 510 # Recorded independently of whether an "mz_cal" row was 511 # written, so a reader can trust this over row presence. 512 raw_ms_dataset.attrs["is_calibrated"] = bool( 513 mass_spectrum.is_calibrated 514 ) 515 516 # Add metadata to the raw_ms dataset when it exists 517 raw_ms_dataset.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 518 519 if isinstance(mass_spectrum, MassSpecfromFreq): 520 raw_ms_dataset.attrs["TransientSetting"] = json.dumps( 521 setting_dicts.get("TransientSetting"), 522 sort_keys=False, 523 indent=4, 524 separators=(",", ": "), 525 ) 526 else: 527 # For centroided data, store metadata directly on the scan group 528 scan_group.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 529 530 if isinstance(mass_spectrum, MassSpecfromFreq): 531 scan_group.attrs["TransientSetting"] = json.dumps( 532 setting_dicts.get("TransientSetting"), 533 sort_keys=False, 534 indent=4, 535 separators=(",", ": "), 536 ) 537 538 else: 539 scan_group = hdf_handle.get(group_key) 540 541 # Next processed-data index: one past the highest existing numeric 542 # key, or 0 if there are none. "raw_ms" and "peak_profile_indices_*" 543 # are not processed-data indices (get_time_index_to_pull also only 544 # considers numeric keys, for the same reason). Using the max rather 545 # than a count matters for a legacy file that already has "raw_ms" 546 # and "1" (an old off-by-one: pre-fix code counted "raw_ms" as an 547 # occupied slot) -- counting numeric keys would give "1" again and 548 # collide with the existing one; max+1 gives "2". 549 numeric_keys = [int(k) for k in scan_group.keys() if k.isdigit()] 550 index_processed_data = str(max(numeric_keys) + 1) if numeric_keys else "0" 551 552 # Per-peak (left, apex, right) indices into the profile arrays above, 553 # in mspeaks order, so a reloaded centroid-with-profile mass 554 # spectrum doesn't need to re-derive peak boundaries from the peak 555 # table's row position (which isn't a profile index). Written once 556 # per peak-table version (same suffix as the processed-data index 557 # below), since the profile itself (raw_ms) doesn't change across 558 # reprocessing but the picked peaks do -- except raw_ms itself is 559 # only ever written on the group's first creation (just above), so 560 # if *this* mass_spectrum's profile is a different length (e.g. a 561 # later reprocessing that re-picked over different raw data), these 562 # indices would point into a profile that was never stored. Skip 563 # writing them in that case; the reader falls back to a search. 564 existing_raw_ms = scan_group.get("raw_ms") 565 if ( 566 not mass_spectrum.is_centroid 567 and export_raw 568 and existing_raw_ms is not None 569 and existing_raw_ms.shape[-1] == len(mass_spectrum.abundance_profile) 570 ): 571 peak_indices = np.array( 572 [ 573 ( 574 mspeak.peak_left_index, 575 mspeak.peak_apex_index, 576 mspeak.peak_right_index, 577 ) 578 for mspeak in mass_spectrum.mspeaks 579 ], 580 dtype=np.int32, 581 ).reshape(-1, 3) 582 scan_group.create_dataset( 583 f"peak_profile_indices_{index_processed_data}", 584 data=peak_indices, 585 **hdf5_dataset_kwargs(peak_indices.shape, compression=compression), 586 ) 587 588 timenow = str(datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z")) 589 590 # Convert list_results to numpy array and optimize data types 591 array_results = np.array(list_results) 592 593 # Apply data type optimization for numeric columns 594 if array_results.dtype == np.float64: 595 array_results = array_results.astype(np.float32) 596 elif array_results.dtype == np.int64: 597 array_results = array_results.astype(np.int32) 598 599 processed_dset = scan_group.create_dataset( 600 index_processed_data, 601 data=array_results, 602 **hdf5_dataset_kwargs(array_results.shape, compression=compression), 603 ) 604 605 processed_dset.attrs["date_utc"] = timenow 606 607 processed_dset.attrs["ColumnsLabels"] = columns_labels 608 609 processed_dset.attrs["MoleculaSearchSetting"] = json.dumps( 610 setting_dicts.get("MoleculaSearch"), 611 sort_keys=False, 612 indent=4, 613 separators=(",", ": "), 614 ) 615 616 processed_dset.attrs["MassSpecPeakSetting"] = json.dumps( 617 setting_dicts.get("MassSpecPeak"), 618 sort_keys=False, 619 indent=4, 620 separators=(",", ": "), 621 ) 622 623 processed_dset.attrs["MassSpectrumSetting"] = json.dumps( 624 setting_dicts.get("MassSpectrum"), 625 sort_keys=False, 626 indent=4, 627 separators=(",", ": "), 628 ) 629 630 def to_hdf(self, compression=DEFAULT_HDF5_COMPRESSION, profile_dtype="f4"): 631 """Exports the mass spectrum data to an HDF5 file. 632 633 Parameters 634 ---------- 635 compression : int, str or None, optional 636 Compression for the written datasets: a gzip level 0-9, 637 ``"lzf"``, or ``None``/``"none"``. Default is 4. ``"lzf"`` is 638 h5py's own filter, not a standard HDF5 one -- it needs h5py (or 639 a tool with the lzf plugin) to read the result back. 640 profile_dtype : str, optional 641 dtype used for the stored profile m/z/abundance arrays 642 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 643 """ 644 645 with h5py.File(self.output_file.with_suffix(".hdf5"), "a") as hdf_handle: 646 self.add_mass_spectrum_to_hdf5( 647 hdf_handle, 648 self.mass_spectrum, 649 str(self.mass_spectrum.scan_number), 650 compression=compression, 651 profile_dtype=profile_dtype, 652 ) 653 654 def parameters_to_toml(self): 655 """Converts the mass spectrum parameters to a TOML string. 656 657 Returns 658 ------- 659 str 660 The TOML string of the mass spectrum parameters. 661 """ 662 663 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 664 665 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 666 dict_setting["analyzer"] = self.mass_spectrum.analyzer 667 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 668 dict_setting["sample_name"] = self.mass_spectrum.sample_name 669 670 output = _dumps_toml(dict_setting) 671 672 return output 673 674 def parameters_to_json(self): 675 """Converts the mass spectrum parameters to a JSON string. 676 677 Returns 678 ------- 679 str 680 The JSON string of the mass spectrum parameters. 681 """ 682 683 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 684 685 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 686 dict_setting["analyzer"] = self.mass_spectrum.analyzer 687 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 688 dict_setting["sample_name"] = self.mass_spectrum.sample_name 689 690 output = json.dumps(dict_setting) 691 692 return output 693 694 def get_mass_spec_attrs(self, mass_spectrum): 695 """Returns the mass spectrum attributes as a dictionary. 696 697 Parameters 698 ---------- 699 mass_spectrum : MassSpectrum 700 The mass spectrum to export. 701 702 Returns 703 ------- 704 dict 705 The mass spectrum attributes. 706 """ 707 708 dict_ms_attrs = {} 709 dict_ms_attrs["polarity"] = mass_spectrum.polarity 710 dict_ms_attrs["rt"] = mass_spectrum.retention_time 711 dict_ms_attrs["tic"] = mass_spectrum.tic 712 dict_ms_attrs["mobility_scan"] = mass_spectrum.mobility_scan 713 dict_ms_attrs["mobility_rt"] = mass_spectrum.mobility_rt 714 dict_ms_attrs["Aterm"] = mass_spectrum.Aterm 715 dict_ms_attrs["Bterm"] = mass_spectrum.Bterm 716 dict_ms_attrs["Cterm"] = mass_spectrum.Cterm 717 dict_ms_attrs["baseline_noise"] = mass_spectrum.baseline_noise 718 dict_ms_attrs["baseline_noise_std"] = mass_spectrum.baseline_noise_std 719 720 return dict_ms_attrs 721 722 def get_all_used_atoms_in_order(self, mass_spectrum): 723 """Returns the list of assigned atoms in the order specified by Atoms.atoms_order list. 724 725 Parameters 726 ---------- 727 mass_spectrum : MassSpectrum 728 The mass spectrum to export. 729 730 Returns 731 ------- 732 list 733 The list of assigned atoms in the order specified by Atoms.atoms_order list. 734 """ 735 736 atoms_in_order = Atoms.atoms_order 737 all_used_atoms = set() 738 if mass_spectrum: 739 for ms_peak in mass_spectrum: 740 if ms_peak: 741 for m_formula in ms_peak: 742 for atom in m_formula.atoms: 743 all_used_atoms.add(atom) 744 745 def sort_method(atom): 746 return [atoms_in_order.index(atom)] 747 748 return sorted(all_used_atoms, key=sort_method) 749 750 def list_dict_to_list(self, mass_spectrum, is_hdf5=False): 751 """Returns the mass spectrum data as a list of dictionaries. 752 753 Parameters 754 ---------- 755 mass_spectrum : MassSpectrum 756 The mass spectrum to export. 757 is_hdf5 : bool, optional 758 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 759 760 Returns 761 ------- 762 list 763 The mass spectrum data as a list of dictionaries. 764 """ 765 766 column_labels = self.columns_label + self.get_all_used_atoms_in_order( 767 mass_spectrum 768 ) 769 770 dict_list = self.get_list_dict_data(mass_spectrum, is_hdf5=is_hdf5) 771 772 all_lines = [] 773 for dict_res in dict_list: 774 result_line = [NaN] * len(column_labels) 775 776 for label, value in dict_res.items(): 777 label_index = column_labels.index(label) 778 result_line[label_index] = value 779 780 all_lines.append(result_line) 781 782 return all_lines 783 784 def get_list_dict_data( 785 self, 786 mass_spectrum, 787 include_no_match=True, 788 include_isotopologues=True, 789 isotopologue_inline=True, 790 no_match_inline=False, 791 is_hdf5=False, 792 additional_columns=None, 793 ): 794 """Returns the mass spectrum data as a list of dictionaries. 795 796 Parameters 797 ---------- 798 mass_spectrum : MassSpectrum 799 The mass spectrum to export. 800 include_no_match : bool, optional 801 Whether to include unassigned (no match) data. Defaults to True. 802 include_isotopologues : bool, optional 803 Whether to include isotopologues. Defaults to True. 804 isotopologue_inline : bool, optional 805 Whether to include isotopologues inline. Defaults to True. 806 no_match_inline : bool, optional 807 Whether to include unassigned (no match) data inline. Defaults to False. 808 is_hdf5 : bool, optional 809 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 810 811 Returns 812 ------- 813 list 814 The mass spectrum data as a list of dictionaries. 815 """ 816 817 dict_data_list = [] 818 819 if is_hdf5: 820 encode = ".encode('utf-8')" 821 else: 822 encode = "" 823 824 def add_no_match_dict_data(index, ms_peak): 825 """ 826 Export dictionary of mspeak info for unassigned (no match) data 827 """ 828 dict_result = { 829 "Index": index, 830 "m/z": ms_peak._mz_exp, 831 "Calibrated m/z": ms_peak.mz_exp, 832 "Peak Height": ms_peak.abundance, 833 "Peak Area": ms_peak.area, 834 "Resolving Power": ms_peak.resolving_power, 835 "S/N": ms_peak.signal_to_noise, 836 # Unassigned: polarity only (±1); no formula charge exists yet 837 "Ion Charge": ms_peak.polarity, 838 "Heteroatom Class": eval("Labels.unassigned{}".format(encode)), 839 } 840 841 dict_data_list.append(dict_result) 842 843 def add_match_dict_data(index, ms_peak, mformula, additional_columns=None): 844 """ 845 Export dictionary of mspeak info for assigned (match) data 846 """ 847 formula_dict = mformula.to_dict() 848 849 dict_result = { 850 "Index": index, 851 "m/z": ms_peak._mz_exp, 852 "Calibrated m/z": ms_peak.mz_exp, 853 "Calculated m/z": mformula.mz_calc, 854 "Peak Height": ms_peak.abundance, 855 "Peak Area": ms_peak.area, 856 "Resolving Power": ms_peak.resolving_power, 857 "S/N": ms_peak.signal_to_noise, 858 # Assigned: formula charge state (may be multi-charge); not peak polarity 859 "Ion Charge": mformula.ion_charge, 860 "m/z Error (ppm)": mformula.mz_error, 861 "Confidence Score": mformula.confidence_score, 862 "Isotopologue Similarity": mformula.isotopologue_similarity, 863 "m/z Error Score": mformula.average_mz_error_score, 864 "DBE": mformula.dbe, 865 "Heteroatom Class": eval("mformula.class_label{}".format(encode)), 866 "H/C": mformula.H_C, 867 "O/C": mformula.O_C, 868 "Ion Type": eval("mformula.ion_type.lower(){}".format(encode)), 869 "Is Isotopologue": int(mformula.is_isotopologue), 870 "Molecular Formula": eval("mformula.string{}".format(encode)), 871 } 872 if additional_columns is not None: 873 possible_dict = { 874 "Aromaticity Index": mformula.A_I, 875 "NOSC": mformula.nosc, 876 "Aromaticity Index (modified)": mformula.A_I_mod, 877 } 878 for column in additional_columns: 879 dict_result[column] = possible_dict.get(column) 880 881 if mformula.adduct_atom: 882 dict_result["Adduct"] = eval("mformula.adduct_atom{}".format(encode)) 883 884 if mformula.is_isotopologue: 885 dict_result["Mono Isotopic Index"] = mformula.mspeak_index_mono_isotopic 886 887 if self.atoms_order_list is None: 888 atoms_order_list = self.get_all_used_atoms_in_order(mass_spectrum) 889 else: 890 atoms_order_list = self.atoms_order_list 891 892 for atom in atoms_order_list: 893 if atom in formula_dict.keys(): 894 dict_result[atom] = formula_dict.get(atom) 895 896 dict_data_list.append(dict_result) 897 898 score_methods = mass_spectrum.molecular_search_settings.score_methods 899 selected_score_method = ( 900 mass_spectrum.molecular_search_settings.output_score_method 901 ) 902 903 if selected_score_method in score_methods: 904 # temp set score method as the one chosen in the output 905 current_method = mass_spectrum.molecular_search_settings.score_method 906 mass_spectrum.molecular_search_settings.score_method = selected_score_method 907 908 for index, ms_peak in enumerate(mass_spectrum): 909 # print(ms_peak.mz_exp) 910 911 if ms_peak: 912 m_formula = ms_peak.best_molecular_formula_candidate 913 914 if m_formula: 915 if not m_formula.is_isotopologue: 916 add_match_dict_data( 917 index, 918 ms_peak, 919 m_formula, 920 additional_columns=additional_columns, 921 ) 922 923 for ( 924 iso_mspeak_index, 925 iso_mf_formula, 926 ) in m_formula.mspeak_mf_isotopologues_indexes: 927 iso_ms_peak = mass_spectrum[iso_mspeak_index] 928 add_match_dict_data( 929 iso_mspeak_index, 930 iso_ms_peak, 931 iso_mf_formula, 932 additional_columns=additional_columns, 933 ) 934 else: 935 if include_no_match and no_match_inline: 936 add_no_match_dict_data(index, ms_peak) 937 938 if include_no_match and not no_match_inline: 939 for index, ms_peak in enumerate(mass_spectrum): 940 if not ms_peak: 941 add_no_match_dict_data(index, ms_peak) 942 # reset score method as the one chosen in the output 943 mass_spectrum.molecular_search_settings.score_method = current_method 944 945 else: 946 for index, ms_peak in enumerate(mass_spectrum): 947 # check if there is a molecular formula candidate for the msPeak 948 949 if ms_peak: 950 # m_formula = ms_peak.molecular_formula_lowest_error 951 for m_formula in ms_peak: 952 if mass_spectrum.molecular_search_settings.output_min_score > 0: 953 if ( 954 m_formula.confidence_score 955 >= mass_spectrum.molecular_search_settings.output_min_score 956 ): 957 if m_formula.is_isotopologue: # isotopologues inline 958 if include_isotopologues and isotopologue_inline: 959 add_match_dict_data( 960 index, 961 ms_peak, 962 m_formula, 963 additional_columns=additional_columns, 964 ) 965 else: 966 add_match_dict_data( 967 index, 968 ms_peak, 969 m_formula, 970 additional_columns=additional_columns, 971 ) # add monoisotopic peak 972 973 # cutoff because of low score 974 else: 975 add_no_match_dict_data(index, ms_peak) 976 977 else: 978 if m_formula.is_isotopologue: # isotopologues inline 979 if include_isotopologues and isotopologue_inline: 980 add_match_dict_data( 981 index, 982 ms_peak, 983 m_formula, 984 additional_columns=additional_columns, 985 ) 986 else: 987 add_match_dict_data( 988 index, 989 ms_peak, 990 m_formula, 991 additional_columns=additional_columns, 992 ) # add monoisotopic peak 993 else: 994 # include not_match 995 if include_no_match and no_match_inline: 996 add_no_match_dict_data(index, ms_peak) 997 998 if include_isotopologues and not isotopologue_inline: 999 for index, ms_peak in enumerate(mass_spectrum): 1000 for m_formula in ms_peak: 1001 if m_formula.is_isotopologue: 1002 if ( 1003 m_formula.confidence_score 1004 >= mass_spectrum.molecular_search_settings.output_min_score 1005 ): 1006 add_match_dict_data( 1007 index, 1008 ms_peak, 1009 m_formula, 1010 additional_columns=additional_columns, 1011 ) 1012 1013 if include_no_match and not no_match_inline: 1014 for index, ms_peak in enumerate(mass_spectrum): 1015 if not ms_peak: 1016 add_no_match_dict_data(index, ms_peak) 1017 1018 # remove duplicated add_match data possibly introduced on the output_score_filter step 1019 res = [] 1020 [res.append(x) for x in dict_data_list if x not in res] 1021 1022 return res
26def hdf5_dataset_kwargs(shape, compression=DEFAULT_HDF5_COMPRESSION, vlen_str=False): 27 """Build ``h5py.Group.create_dataset`` filter kwargs for a compression choice. 28 29 Centralizes the HDF5 writers' compression choice (one helper used by every 30 ``create_dataset`` call on the mass spectrum / LC-MS export path) and the 31 filter exceptions h5py enforces: it refuses chunk/filter options on scalar 32 (``shape == ()``) or zero-length datasets, and the fletcher32 checksum 33 filter gains nothing (and h5py can reject it) on variable-length string 34 datasets. 35 36 Parameters 37 ---------- 38 shape : tuple 39 Shape of the data about to be written. 40 compression : int, str or None, optional 41 A gzip level 0-9, ``"lzf"``, or ``None``/``"none"`` for no 42 compression. Default is 4. ``"lzf"`` is h5py's own filter, not part 43 of core HDF5 -- a file written with it needs h5py (or another tool 44 with the lzf plugin available) to read the compressed datasets 45 back; gzip is the portable choice across other HDF5 tools. 46 vlen_str : bool, optional 47 True when the dataset's dtype is a variable-length string 48 (``h5py.string_dtype()``); skips the shuffle/fletcher32 filters that 49 don't apply to vlen data. Default is False. 50 51 Returns 52 ------- 53 dict 54 Keyword arguments to splat into ``create_dataset`` (besides ``data`` 55 and ``dtype``). 56 """ 57 if not shape or 0 in shape: 58 return {} 59 60 kwargs = {"chunks": True} 61 62 if compression not in (None, "none"): 63 if compression == "lzf": 64 kwargs["compression"] = "lzf" 65 else: 66 kwargs["compression"] = "gzip" 67 kwargs["compression_opts"] = int(compression) 68 69 if not vlen_str: 70 kwargs["shuffle"] = True 71 kwargs["fletcher32"] = True 72 73 return kwargs
Build h5py.Group.create_dataset filter kwargs for a compression choice.
Centralizes the HDF5 writers' compression choice (one helper used by every
create_dataset call on the mass spectrum / LC-MS export path) and the
filter exceptions h5py enforces: it refuses chunk/filter options on scalar
(shape == ()) or zero-length datasets, and the fletcher32 checksum
filter gains nothing (and h5py can reject it) on variable-length string
datasets.
Parameters
- shape (tuple): Shape of the data about to be written.
- compression (int, str or None, optional):
A gzip level 0-9,
"lzf", orNone/"none"for no compression. Default is 4."lzf"is h5py's own filter, not part of core HDF5 -- a file written with it needs h5py (or another tool with the lzf plugin available) to read the compressed datasets back; gzip is the portable choice across other HDF5 tools. - vlen_str (bool, optional):
True when the dataset's dtype is a variable-length string
(
h5py.string_dtype()); skips the shuffle/fletcher32 filters that don't apply to vlen data. Default is False.
Returns
- dict: Keyword arguments to splat into
create_dataset(besidesdataanddtype).
76class HighResMassSpecExport(Thread): 77 """A class for exporting high-resolution mass spectra. 78 79 Parameters 80 ---------- 81 out_file_path : str 82 The output file path. 83 mass_spectrum : MassSpectrum 84 The mass spectrum to export. 85 output_type : str, optional 86 The type of output file. Defaults to 'excel'. Can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'. 87 88 Attributes 89 ---------- 90 output_file : Path 91 The output file path. 92 output_type : str 93 The type of output file. 94 mass_spectrum : MassSpectrum 95 The mass spectrum to export. 96 atoms_order_list : list 97 The list of assigned atoms in the order specified by Atoms.atoms_order list. 98 columns_label : list 99 The column labels in order. 100 101 Methods 102 ------- 103 * save(). 104 Save the mass spectrum data to the output file. 105 * run(). 106 Run the export process. 107 * get_pandas_df(). 108 Returns the mass spectrum data as a pandas DataFrame. 109 * write_settings(output_path, mass_spectrum). 110 Writes the settings of the mass spectrum to a JSON file. 111 * to_pandas(write_metadata=True). 112 Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file. 113 * to_excel(write_metadata=True). 114 Exports the mass spectrum data to an Excel file. 115 * to_csv(write_metadata=True). 116 Exports the mass spectrum data to a CSV file. 117 * to_parquet(write_metadata=True). 118 Exports the mass spectrum data to a Parquet file. 119 * to_json(). 120 Exports the mass spectrum data to a JSON string. 121 * to_hdf(). 122 Exports the mass spectrum data to an HDF5 file. 123 * parameters_to_toml(). 124 Converts the mass spectrum parameters to a TOML string. 125 * parameters_to_json(). 126 Converts the mass spectrum parameters to a JSON string. 127 * get_mass_spec_attrs(mass_spectrum). 128 Returns the mass spectrum attributes as a dictionary. 129 * get_all_used_atoms_in_order(mass_spectrum). 130 Returns the list of assigned atoms in the order specified by Atoms.atoms_order list. 131 * list_dict_to_list(mass_spectrum, is_hdf5=False). 132 Returns the mass spectrum data as a list of dictionaries. 133 * get_list_dict_data(mass_spectrum, include_no_match=True, include_isotopologues=True, isotopologue_inline=True, no_match_inline=False, is_hdf5=False). 134 Returns the mass spectrum data as a list of dictionaries. 135 136 """ 137 138 def __init__(self, out_file_path, mass_spectrum, output_type="excel"): 139 Thread.__init__(self) 140 141 self.output_file = Path(out_file_path) 142 143 # 'excel', 'csv' or 'pandas' 144 self.output_type = output_type 145 146 self.mass_spectrum = mass_spectrum 147 148 # collect all assigned atoms and order them accordingly to the Atoms.atoms_order list 149 self.atoms_order_list = self.get_all_used_atoms_in_order(self.mass_spectrum) 150 151 self._init_columns() 152 153 def _init_columns(self): 154 """Initialize the columns for the mass spectrum output.""" 155 # column labels in order 156 self.columns_label = [ 157 "Index", 158 "m/z", 159 "Calibrated m/z", 160 "Calculated m/z", 161 "Peak Height", 162 "Peak Area", 163 "Resolving Power", 164 "S/N", 165 "Ion Charge", 166 "m/z Error (ppm)", 167 "m/z Error Score", 168 "Isotopologue Similarity", 169 "Confidence Score", 170 "DBE", 171 "O/C", 172 "H/C", 173 "Heteroatom Class", 174 "Ion Type", 175 "Adduct", 176 "Is Isotopologue", 177 "Mono Isotopic Index", 178 "Molecular Formula", 179 ] 180 181 @property 182 def output_type(self): 183 """Returns the output type of the mass spectrum.""" 184 return self._output_type 185 186 @output_type.setter 187 def output_type(self, output_type): 188 output_types = ["excel", "csv", "pandas", "parquet", "hdf5"] 189 if output_type in output_types: 190 self._output_type = output_type 191 else: 192 raise TypeError( 193 'Supported types are "excel", "csv", "pandas", "parquet" or "hdf5", %s entered' 194 % output_type 195 ) 196 197 def save(self): 198 """Save the mass spectrum data to the output file. 199 200 Raises 201 ------ 202 ValueError 203 If the output type is not supported. 204 """ 205 206 if self.output_type == "excel": 207 self.to_excel() 208 elif self.output_type == "csv": 209 self.to_csv() 210 elif self.output_type == "pandas": 211 self.to_pandas() 212 elif self.output_type == "parquet": 213 self.to_parquet() 214 elif self.output_type == "hdf5": 215 self.to_hdf() 216 else: 217 raise ValueError( 218 "Unkown output type: %s; it can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'" 219 % self.output_type 220 ) 221 222 def run(self): 223 """Run the export process. 224 225 This method is called when the thread starts. 226 It calls the save method to perform the export.""" 227 self.save() 228 229 def get_pandas_df(self, additional_columns=None): 230 """Returns the mass spectrum data as a pandas DataFrame. 231 232 Parameters 233 ---------- 234 additional_columns : list, optional 235 Additional columns to include in the DataFrame. Defaults to None. 236 Suitable additional columns are: 'Aromaticity Index', 'NOSC', 'Aromaticity Index (modified)'. 237 238 Returns 239 ------- 240 DataFrame 241 The mass spectrum data as a pandas DataFrame. 242 """ 243 if additional_columns is not None: 244 possible_additional_columns = [ 245 "Aromaticity Index", 246 "NOSC", 247 "Aromaticity Index (modified)", 248 ] 249 if additional_columns: 250 for column in additional_columns: 251 if column not in possible_additional_columns: 252 raise ValueError("Invalid additional column: %s" % column) 253 columns = ( 254 self.columns_label 255 + additional_columns 256 + self.get_all_used_atoms_in_order(self.mass_spectrum) 257 ) 258 else: 259 columns = self.columns_label + self.get_all_used_atoms_in_order( 260 self.mass_spectrum 261 ) 262 dict_data_list = self.get_list_dict_data( 263 self.mass_spectrum, additional_columns=additional_columns 264 ) 265 df = DataFrame(dict_data_list, columns=columns) 266 df.attrs["name"] = self.output_file 267 return df 268 269 def write_settings(self, output_path, mass_spectrum): 270 """Writes the settings of the mass spectrum to a JSON file. 271 272 Parameters 273 ---------- 274 output_path : str 275 The output file path. 276 mass_spectrum : MassSpectrum 277 The mass spectrum to export. 278 """ 279 280 import json 281 282 dict_setting = parameter_to_dict.get_dict_data_ms(mass_spectrum) 283 284 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(mass_spectrum) 285 dict_setting["analyzer"] = mass_spectrum.analyzer 286 dict_setting["instrument_label"] = mass_spectrum.instrument_label 287 dict_setting["sample_name"] = mass_spectrum.sample_name 288 289 with open( 290 output_path.with_suffix(".json"), 291 "w", 292 encoding="utf8", 293 ) as outfile: 294 output = json.dumps( 295 dict_setting, sort_keys=True, indent=4, separators=(",", ": ") 296 ) 297 outfile.write(output) 298 299 def to_pandas(self, write_metadata=True): 300 """Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file. 301 302 Parameters 303 ---------- 304 write_metadata : bool, optional 305 Whether to write the metadata to a JSON file. Defaults to True. 306 """ 307 308 columns = self.columns_label + self.get_all_used_atoms_in_order( 309 self.mass_spectrum 310 ) 311 312 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 313 314 df = DataFrame(dict_data_list, columns=columns) 315 316 df.to_pickle(self.output_file.with_suffix(".pkl")) 317 318 if write_metadata: 319 self.write_settings(self.output_file, self.mass_spectrum) 320 321 def to_excel(self, write_metadata=True): 322 """Exports the mass spectrum data to an Excel file. 323 324 Parameters 325 ---------- 326 write_metadata : bool, optional 327 Whether to write the metadata to a JSON file. Defaults to True. 328 """ 329 330 columns = self.columns_label + self.get_all_used_atoms_in_order( 331 self.mass_spectrum 332 ) 333 334 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 335 336 df = DataFrame(dict_data_list, columns=columns) 337 338 df.to_excel(self.output_file.with_suffix(".xlsx")) 339 340 if write_metadata: 341 self.write_settings(self.output_file, self.mass_spectrum) 342 343 def to_csv(self, write_metadata=True): 344 """Exports the mass spectrum data to a CSV file. 345 346 Parameters 347 ---------- 348 write_metadata : bool, optional 349 Whether to write the metadata to a JSON file. Defaults to True. 350 """ 351 352 columns = self.columns_label + self.get_all_used_atoms_in_order( 353 self.mass_spectrum 354 ) 355 356 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 357 358 import csv 359 360 try: 361 with open(self.output_file.with_suffix(".csv"), "w", newline="") as csvfile: 362 writer = csv.DictWriter(csvfile, fieldnames=columns) 363 writer.writeheader() 364 for data in dict_data_list: 365 writer.writerow(data) 366 if write_metadata: 367 self.write_settings(self.output_file, self.mass_spectrum) 368 369 except IOError as ioerror: 370 print(ioerror) 371 372 def to_parquet(self, write_metadata=True): 373 """Exports the mass spectrum data to a Parquet file. 374 375 Parameters 376 ---------- 377 write_metadata : bool, optional 378 Whether to write the metadata to a JSON file. Defaults to True. 379 380 Notes 381 ----- 382 Requires ``pyarrow`` (or ``fastparquet``) for pandas Parquet I/O. 383 """ 384 385 columns = self.columns_label + self.get_all_used_atoms_in_order( 386 self.mass_spectrum 387 ) 388 389 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 390 391 df = DataFrame(dict_data_list, columns=columns) 392 393 df.to_parquet(self.output_file.with_suffix(".parquet"), index=False) 394 395 if write_metadata: 396 self.write_settings(self.output_file, self.mass_spectrum) 397 398 def to_json(self): 399 """Exports the mass spectrum data to a JSON string.""" 400 401 columns = self.columns_label + self.get_all_used_atoms_in_order( 402 self.mass_spectrum 403 ) 404 405 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 406 407 df = DataFrame(dict_data_list, columns=columns) 408 409 # for key, values in dict_data.items(): 410 # if not values: dict_data[key] = NaN 411 412 # output = json.dumps(dict_data, sort_keys=True, indent=4, separators=(',', ': ')) 413 return df.to_json(orient="records") 414 415 def add_mass_spectrum_to_hdf5( 416 self, 417 hdf_handle, 418 mass_spectrum, 419 group_key, 420 mass_spectra_group=None, 421 export_raw=True, 422 compression=DEFAULT_HDF5_COMPRESSION, 423 profile_dtype="f4", 424 ): 425 """Adds the mass spectrum data to an HDF5 file. 426 427 Parameters 428 ---------- 429 hdf_handle : h5py.File 430 The HDF5 file handle. 431 mass_spectrum : MassSpectrum 432 The mass spectrum to add to the HDF5 file. 433 group_key : str 434 The group key (where to add the mass spectrum data within the HDF5 file). 435 mass_spectra_group : h5py.Group, optional 436 The mass spectra group. Defaults to None (no group, mass spectrum is added to the root). 437 export_raw : bool, optional 438 Whether to export the raw data. Defaults to True. 439 If False, only the processed data (peaks) is exported (essentially centroided data). 440 compression : int, str or None, optional 441 Compression for the written datasets: a gzip level 0-9, ``"lzf"``, 442 or ``None``/``"none"``. Default is 4. ``"lzf"`` is h5py's own 443 filter, not a standard HDF5 one -- it needs h5py (or a tool 444 with the lzf plugin) to read the result back. 445 profile_dtype : str, optional 446 dtype used for the stored profile m/z/abundance arrays 447 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 448 """ 449 if mass_spectra_group is None: 450 # Check if the file has the necessary attributes and add them if not 451 # This assumes that if there is a mass_spectra_group, these attributes were already added to the file 452 if not hdf_handle.attrs.get("date_utc"): 453 timenow = str( 454 datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z") 455 ) 456 hdf_handle.attrs["date_utc"] = timenow 457 hdf_handle.attrs["file_name"] = mass_spectrum.filename.name 458 hdf_handle.attrs["data_structure"] = "mass_spectrum" 459 hdf_handle.attrs["analyzer"] = mass_spectrum.analyzer 460 hdf_handle.attrs["instrument_label"] = mass_spectrum.instrument_label 461 hdf_handle.attrs["sample_name"] = mass_spectrum.sample_name 462 463 list_results = self.list_dict_to_list(mass_spectrum, is_hdf5=True) 464 465 dict_ms_attrs = self.get_mass_spec_attrs(mass_spectrum) 466 467 setting_dicts = parameter_to_dict.get_dict_data_ms(mass_spectrum) 468 469 columns_labels = json.dumps( 470 self.columns_label + self.get_all_used_atoms_in_order(mass_spectrum), 471 sort_keys=False, 472 indent=4, 473 separators=(",", ": "), 474 ) 475 476 group_key = group_key 477 478 if mass_spectra_group is not None: 479 hdf_handle = mass_spectra_group 480 481 if group_key not in hdf_handle.keys(): 482 scan_group = hdf_handle.create_group(group_key) 483 484 # If there is raw data (from profile data) save it 485 if not mass_spectrum.is_centroid and export_raw: 486 # "mz" is always the experimental (uncalibrated) profile; a 487 # separate "mz_cal" row is added only when the spectrum is 488 # actually calibrated. mz_cal_profile alone isn't enough to 489 # tell: MzDomainCalibration.__init__ pre-populates it before 490 # any calibration has run, so a never-calibrated spectrum 491 # can still have a (stale/placeholder) mz_cal_profile. 492 row_labels = ["mz", "abundance"] 493 rows = [ 494 np.asarray(mass_spectrum._mz_exp), 495 np.asarray(mass_spectrum.abundance_profile), 496 ] 497 mz_cal_profile = mass_spectrum.mz_cal_profile 498 if mass_spectrum.is_calibrated and mz_cal_profile is not None: 499 row_labels.append("mz_cal") 500 rows.append(np.asarray(mz_cal_profile)) 501 502 mz_abun_array = np.array(rows, dtype=profile_dtype) 503 504 raw_ms_dataset = scan_group.create_dataset( 505 "raw_ms", 506 data=mz_abun_array, 507 dtype=profile_dtype, 508 **hdf5_dataset_kwargs(mz_abun_array.shape, compression=compression), 509 ) 510 raw_ms_dataset.attrs["row_labels"] = json.dumps(row_labels) 511 # Recorded independently of whether an "mz_cal" row was 512 # written, so a reader can trust this over row presence. 513 raw_ms_dataset.attrs["is_calibrated"] = bool( 514 mass_spectrum.is_calibrated 515 ) 516 517 # Add metadata to the raw_ms dataset when it exists 518 raw_ms_dataset.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 519 520 if isinstance(mass_spectrum, MassSpecfromFreq): 521 raw_ms_dataset.attrs["TransientSetting"] = json.dumps( 522 setting_dicts.get("TransientSetting"), 523 sort_keys=False, 524 indent=4, 525 separators=(",", ": "), 526 ) 527 else: 528 # For centroided data, store metadata directly on the scan group 529 scan_group.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 530 531 if isinstance(mass_spectrum, MassSpecfromFreq): 532 scan_group.attrs["TransientSetting"] = json.dumps( 533 setting_dicts.get("TransientSetting"), 534 sort_keys=False, 535 indent=4, 536 separators=(",", ": "), 537 ) 538 539 else: 540 scan_group = hdf_handle.get(group_key) 541 542 # Next processed-data index: one past the highest existing numeric 543 # key, or 0 if there are none. "raw_ms" and "peak_profile_indices_*" 544 # are not processed-data indices (get_time_index_to_pull also only 545 # considers numeric keys, for the same reason). Using the max rather 546 # than a count matters for a legacy file that already has "raw_ms" 547 # and "1" (an old off-by-one: pre-fix code counted "raw_ms" as an 548 # occupied slot) -- counting numeric keys would give "1" again and 549 # collide with the existing one; max+1 gives "2". 550 numeric_keys = [int(k) for k in scan_group.keys() if k.isdigit()] 551 index_processed_data = str(max(numeric_keys) + 1) if numeric_keys else "0" 552 553 # Per-peak (left, apex, right) indices into the profile arrays above, 554 # in mspeaks order, so a reloaded centroid-with-profile mass 555 # spectrum doesn't need to re-derive peak boundaries from the peak 556 # table's row position (which isn't a profile index). Written once 557 # per peak-table version (same suffix as the processed-data index 558 # below), since the profile itself (raw_ms) doesn't change across 559 # reprocessing but the picked peaks do -- except raw_ms itself is 560 # only ever written on the group's first creation (just above), so 561 # if *this* mass_spectrum's profile is a different length (e.g. a 562 # later reprocessing that re-picked over different raw data), these 563 # indices would point into a profile that was never stored. Skip 564 # writing them in that case; the reader falls back to a search. 565 existing_raw_ms = scan_group.get("raw_ms") 566 if ( 567 not mass_spectrum.is_centroid 568 and export_raw 569 and existing_raw_ms is not None 570 and existing_raw_ms.shape[-1] == len(mass_spectrum.abundance_profile) 571 ): 572 peak_indices = np.array( 573 [ 574 ( 575 mspeak.peak_left_index, 576 mspeak.peak_apex_index, 577 mspeak.peak_right_index, 578 ) 579 for mspeak in mass_spectrum.mspeaks 580 ], 581 dtype=np.int32, 582 ).reshape(-1, 3) 583 scan_group.create_dataset( 584 f"peak_profile_indices_{index_processed_data}", 585 data=peak_indices, 586 **hdf5_dataset_kwargs(peak_indices.shape, compression=compression), 587 ) 588 589 timenow = str(datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z")) 590 591 # Convert list_results to numpy array and optimize data types 592 array_results = np.array(list_results) 593 594 # Apply data type optimization for numeric columns 595 if array_results.dtype == np.float64: 596 array_results = array_results.astype(np.float32) 597 elif array_results.dtype == np.int64: 598 array_results = array_results.astype(np.int32) 599 600 processed_dset = scan_group.create_dataset( 601 index_processed_data, 602 data=array_results, 603 **hdf5_dataset_kwargs(array_results.shape, compression=compression), 604 ) 605 606 processed_dset.attrs["date_utc"] = timenow 607 608 processed_dset.attrs["ColumnsLabels"] = columns_labels 609 610 processed_dset.attrs["MoleculaSearchSetting"] = json.dumps( 611 setting_dicts.get("MoleculaSearch"), 612 sort_keys=False, 613 indent=4, 614 separators=(",", ": "), 615 ) 616 617 processed_dset.attrs["MassSpecPeakSetting"] = json.dumps( 618 setting_dicts.get("MassSpecPeak"), 619 sort_keys=False, 620 indent=4, 621 separators=(",", ": "), 622 ) 623 624 processed_dset.attrs["MassSpectrumSetting"] = json.dumps( 625 setting_dicts.get("MassSpectrum"), 626 sort_keys=False, 627 indent=4, 628 separators=(",", ": "), 629 ) 630 631 def to_hdf(self, compression=DEFAULT_HDF5_COMPRESSION, profile_dtype="f4"): 632 """Exports the mass spectrum data to an HDF5 file. 633 634 Parameters 635 ---------- 636 compression : int, str or None, optional 637 Compression for the written datasets: a gzip level 0-9, 638 ``"lzf"``, or ``None``/``"none"``. Default is 4. ``"lzf"`` is 639 h5py's own filter, not a standard HDF5 one -- it needs h5py (or 640 a tool with the lzf plugin) to read the result back. 641 profile_dtype : str, optional 642 dtype used for the stored profile m/z/abundance arrays 643 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 644 """ 645 646 with h5py.File(self.output_file.with_suffix(".hdf5"), "a") as hdf_handle: 647 self.add_mass_spectrum_to_hdf5( 648 hdf_handle, 649 self.mass_spectrum, 650 str(self.mass_spectrum.scan_number), 651 compression=compression, 652 profile_dtype=profile_dtype, 653 ) 654 655 def parameters_to_toml(self): 656 """Converts the mass spectrum parameters to a TOML string. 657 658 Returns 659 ------- 660 str 661 The TOML string of the mass spectrum parameters. 662 """ 663 664 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 665 666 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 667 dict_setting["analyzer"] = self.mass_spectrum.analyzer 668 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 669 dict_setting["sample_name"] = self.mass_spectrum.sample_name 670 671 output = _dumps_toml(dict_setting) 672 673 return output 674 675 def parameters_to_json(self): 676 """Converts the mass spectrum parameters to a JSON string. 677 678 Returns 679 ------- 680 str 681 The JSON string of the mass spectrum parameters. 682 """ 683 684 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 685 686 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 687 dict_setting["analyzer"] = self.mass_spectrum.analyzer 688 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 689 dict_setting["sample_name"] = self.mass_spectrum.sample_name 690 691 output = json.dumps(dict_setting) 692 693 return output 694 695 def get_mass_spec_attrs(self, mass_spectrum): 696 """Returns the mass spectrum attributes as a dictionary. 697 698 Parameters 699 ---------- 700 mass_spectrum : MassSpectrum 701 The mass spectrum to export. 702 703 Returns 704 ------- 705 dict 706 The mass spectrum attributes. 707 """ 708 709 dict_ms_attrs = {} 710 dict_ms_attrs["polarity"] = mass_spectrum.polarity 711 dict_ms_attrs["rt"] = mass_spectrum.retention_time 712 dict_ms_attrs["tic"] = mass_spectrum.tic 713 dict_ms_attrs["mobility_scan"] = mass_spectrum.mobility_scan 714 dict_ms_attrs["mobility_rt"] = mass_spectrum.mobility_rt 715 dict_ms_attrs["Aterm"] = mass_spectrum.Aterm 716 dict_ms_attrs["Bterm"] = mass_spectrum.Bterm 717 dict_ms_attrs["Cterm"] = mass_spectrum.Cterm 718 dict_ms_attrs["baseline_noise"] = mass_spectrum.baseline_noise 719 dict_ms_attrs["baseline_noise_std"] = mass_spectrum.baseline_noise_std 720 721 return dict_ms_attrs 722 723 def get_all_used_atoms_in_order(self, mass_spectrum): 724 """Returns the list of assigned atoms in the order specified by Atoms.atoms_order list. 725 726 Parameters 727 ---------- 728 mass_spectrum : MassSpectrum 729 The mass spectrum to export. 730 731 Returns 732 ------- 733 list 734 The list of assigned atoms in the order specified by Atoms.atoms_order list. 735 """ 736 737 atoms_in_order = Atoms.atoms_order 738 all_used_atoms = set() 739 if mass_spectrum: 740 for ms_peak in mass_spectrum: 741 if ms_peak: 742 for m_formula in ms_peak: 743 for atom in m_formula.atoms: 744 all_used_atoms.add(atom) 745 746 def sort_method(atom): 747 return [atoms_in_order.index(atom)] 748 749 return sorted(all_used_atoms, key=sort_method) 750 751 def list_dict_to_list(self, mass_spectrum, is_hdf5=False): 752 """Returns the mass spectrum data as a list of dictionaries. 753 754 Parameters 755 ---------- 756 mass_spectrum : MassSpectrum 757 The mass spectrum to export. 758 is_hdf5 : bool, optional 759 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 760 761 Returns 762 ------- 763 list 764 The mass spectrum data as a list of dictionaries. 765 """ 766 767 column_labels = self.columns_label + self.get_all_used_atoms_in_order( 768 mass_spectrum 769 ) 770 771 dict_list = self.get_list_dict_data(mass_spectrum, is_hdf5=is_hdf5) 772 773 all_lines = [] 774 for dict_res in dict_list: 775 result_line = [NaN] * len(column_labels) 776 777 for label, value in dict_res.items(): 778 label_index = column_labels.index(label) 779 result_line[label_index] = value 780 781 all_lines.append(result_line) 782 783 return all_lines 784 785 def get_list_dict_data( 786 self, 787 mass_spectrum, 788 include_no_match=True, 789 include_isotopologues=True, 790 isotopologue_inline=True, 791 no_match_inline=False, 792 is_hdf5=False, 793 additional_columns=None, 794 ): 795 """Returns the mass spectrum data as a list of dictionaries. 796 797 Parameters 798 ---------- 799 mass_spectrum : MassSpectrum 800 The mass spectrum to export. 801 include_no_match : bool, optional 802 Whether to include unassigned (no match) data. Defaults to True. 803 include_isotopologues : bool, optional 804 Whether to include isotopologues. Defaults to True. 805 isotopologue_inline : bool, optional 806 Whether to include isotopologues inline. Defaults to True. 807 no_match_inline : bool, optional 808 Whether to include unassigned (no match) data inline. Defaults to False. 809 is_hdf5 : bool, optional 810 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 811 812 Returns 813 ------- 814 list 815 The mass spectrum data as a list of dictionaries. 816 """ 817 818 dict_data_list = [] 819 820 if is_hdf5: 821 encode = ".encode('utf-8')" 822 else: 823 encode = "" 824 825 def add_no_match_dict_data(index, ms_peak): 826 """ 827 Export dictionary of mspeak info for unassigned (no match) data 828 """ 829 dict_result = { 830 "Index": index, 831 "m/z": ms_peak._mz_exp, 832 "Calibrated m/z": ms_peak.mz_exp, 833 "Peak Height": ms_peak.abundance, 834 "Peak Area": ms_peak.area, 835 "Resolving Power": ms_peak.resolving_power, 836 "S/N": ms_peak.signal_to_noise, 837 # Unassigned: polarity only (±1); no formula charge exists yet 838 "Ion Charge": ms_peak.polarity, 839 "Heteroatom Class": eval("Labels.unassigned{}".format(encode)), 840 } 841 842 dict_data_list.append(dict_result) 843 844 def add_match_dict_data(index, ms_peak, mformula, additional_columns=None): 845 """ 846 Export dictionary of mspeak info for assigned (match) data 847 """ 848 formula_dict = mformula.to_dict() 849 850 dict_result = { 851 "Index": index, 852 "m/z": ms_peak._mz_exp, 853 "Calibrated m/z": ms_peak.mz_exp, 854 "Calculated m/z": mformula.mz_calc, 855 "Peak Height": ms_peak.abundance, 856 "Peak Area": ms_peak.area, 857 "Resolving Power": ms_peak.resolving_power, 858 "S/N": ms_peak.signal_to_noise, 859 # Assigned: formula charge state (may be multi-charge); not peak polarity 860 "Ion Charge": mformula.ion_charge, 861 "m/z Error (ppm)": mformula.mz_error, 862 "Confidence Score": mformula.confidence_score, 863 "Isotopologue Similarity": mformula.isotopologue_similarity, 864 "m/z Error Score": mformula.average_mz_error_score, 865 "DBE": mformula.dbe, 866 "Heteroatom Class": eval("mformula.class_label{}".format(encode)), 867 "H/C": mformula.H_C, 868 "O/C": mformula.O_C, 869 "Ion Type": eval("mformula.ion_type.lower(){}".format(encode)), 870 "Is Isotopologue": int(mformula.is_isotopologue), 871 "Molecular Formula": eval("mformula.string{}".format(encode)), 872 } 873 if additional_columns is not None: 874 possible_dict = { 875 "Aromaticity Index": mformula.A_I, 876 "NOSC": mformula.nosc, 877 "Aromaticity Index (modified)": mformula.A_I_mod, 878 } 879 for column in additional_columns: 880 dict_result[column] = possible_dict.get(column) 881 882 if mformula.adduct_atom: 883 dict_result["Adduct"] = eval("mformula.adduct_atom{}".format(encode)) 884 885 if mformula.is_isotopologue: 886 dict_result["Mono Isotopic Index"] = mformula.mspeak_index_mono_isotopic 887 888 if self.atoms_order_list is None: 889 atoms_order_list = self.get_all_used_atoms_in_order(mass_spectrum) 890 else: 891 atoms_order_list = self.atoms_order_list 892 893 for atom in atoms_order_list: 894 if atom in formula_dict.keys(): 895 dict_result[atom] = formula_dict.get(atom) 896 897 dict_data_list.append(dict_result) 898 899 score_methods = mass_spectrum.molecular_search_settings.score_methods 900 selected_score_method = ( 901 mass_spectrum.molecular_search_settings.output_score_method 902 ) 903 904 if selected_score_method in score_methods: 905 # temp set score method as the one chosen in the output 906 current_method = mass_spectrum.molecular_search_settings.score_method 907 mass_spectrum.molecular_search_settings.score_method = selected_score_method 908 909 for index, ms_peak in enumerate(mass_spectrum): 910 # print(ms_peak.mz_exp) 911 912 if ms_peak: 913 m_formula = ms_peak.best_molecular_formula_candidate 914 915 if m_formula: 916 if not m_formula.is_isotopologue: 917 add_match_dict_data( 918 index, 919 ms_peak, 920 m_formula, 921 additional_columns=additional_columns, 922 ) 923 924 for ( 925 iso_mspeak_index, 926 iso_mf_formula, 927 ) in m_formula.mspeak_mf_isotopologues_indexes: 928 iso_ms_peak = mass_spectrum[iso_mspeak_index] 929 add_match_dict_data( 930 iso_mspeak_index, 931 iso_ms_peak, 932 iso_mf_formula, 933 additional_columns=additional_columns, 934 ) 935 else: 936 if include_no_match and no_match_inline: 937 add_no_match_dict_data(index, ms_peak) 938 939 if include_no_match and not no_match_inline: 940 for index, ms_peak in enumerate(mass_spectrum): 941 if not ms_peak: 942 add_no_match_dict_data(index, ms_peak) 943 # reset score method as the one chosen in the output 944 mass_spectrum.molecular_search_settings.score_method = current_method 945 946 else: 947 for index, ms_peak in enumerate(mass_spectrum): 948 # check if there is a molecular formula candidate for the msPeak 949 950 if ms_peak: 951 # m_formula = ms_peak.molecular_formula_lowest_error 952 for m_formula in ms_peak: 953 if mass_spectrum.molecular_search_settings.output_min_score > 0: 954 if ( 955 m_formula.confidence_score 956 >= mass_spectrum.molecular_search_settings.output_min_score 957 ): 958 if m_formula.is_isotopologue: # isotopologues inline 959 if include_isotopologues and isotopologue_inline: 960 add_match_dict_data( 961 index, 962 ms_peak, 963 m_formula, 964 additional_columns=additional_columns, 965 ) 966 else: 967 add_match_dict_data( 968 index, 969 ms_peak, 970 m_formula, 971 additional_columns=additional_columns, 972 ) # add monoisotopic peak 973 974 # cutoff because of low score 975 else: 976 add_no_match_dict_data(index, ms_peak) 977 978 else: 979 if m_formula.is_isotopologue: # isotopologues inline 980 if include_isotopologues and isotopologue_inline: 981 add_match_dict_data( 982 index, 983 ms_peak, 984 m_formula, 985 additional_columns=additional_columns, 986 ) 987 else: 988 add_match_dict_data( 989 index, 990 ms_peak, 991 m_formula, 992 additional_columns=additional_columns, 993 ) # add monoisotopic peak 994 else: 995 # include not_match 996 if include_no_match and no_match_inline: 997 add_no_match_dict_data(index, ms_peak) 998 999 if include_isotopologues and not isotopologue_inline: 1000 for index, ms_peak in enumerate(mass_spectrum): 1001 for m_formula in ms_peak: 1002 if m_formula.is_isotopologue: 1003 if ( 1004 m_formula.confidence_score 1005 >= mass_spectrum.molecular_search_settings.output_min_score 1006 ): 1007 add_match_dict_data( 1008 index, 1009 ms_peak, 1010 m_formula, 1011 additional_columns=additional_columns, 1012 ) 1013 1014 if include_no_match and not no_match_inline: 1015 for index, ms_peak in enumerate(mass_spectrum): 1016 if not ms_peak: 1017 add_no_match_dict_data(index, ms_peak) 1018 1019 # remove duplicated add_match data possibly introduced on the output_score_filter step 1020 res = [] 1021 [res.append(x) for x in dict_data_list if x not in res] 1022 1023 return res
A class for exporting high-resolution mass spectra.
Parameters
- out_file_path (str): The output file path.
- mass_spectrum (MassSpectrum): The mass spectrum to export.
- output_type (str, optional): The type of output file. Defaults to 'excel'. Can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'.
Attributes
- output_file (Path): The output file path.
- output_type (str): The type of output file.
- mass_spectrum (MassSpectrum): The mass spectrum to export.
- atoms_order_list (list): The list of assigned atoms in the order specified by Atoms.atoms_order list.
- columns_label (list): The column labels in order.
Methods
- save(). Save the mass spectrum data to the output file.
- run(). Run the export process.
- get_pandas_df(). Returns the mass spectrum data as a pandas DataFrame.
- write_settings(output_path, mass_spectrum). Writes the settings of the mass spectrum to a JSON file.
- to_pandas(write_metadata=True). Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file.
- to_excel(write_metadata=True). Exports the mass spectrum data to an Excel file.
- to_csv(write_metadata=True). Exports the mass spectrum data to a CSV file.
- to_parquet(write_metadata=True). Exports the mass spectrum data to a Parquet file.
- to_json(). Exports the mass spectrum data to a JSON string.
- to_hdf(). Exports the mass spectrum data to an HDF5 file.
- parameters_to_toml(). Converts the mass spectrum parameters to a TOML string.
- parameters_to_json(). Converts the mass spectrum parameters to a JSON string.
- get_mass_spec_attrs(mass_spectrum). Returns the mass spectrum attributes as a dictionary.
- get_all_used_atoms_in_order(mass_spectrum). Returns the list of assigned atoms in the order specified by Atoms.atoms_order list.
- list_dict_to_list(mass_spectrum, is_hdf5=False). Returns the mass spectrum data as a list of dictionaries.
- get_list_dict_data(mass_spectrum, include_no_match=True, include_isotopologues=True, isotopologue_inline=True, no_match_inline=False, is_hdf5=False). Returns the mass spectrum data as a list of dictionaries.
138 def __init__(self, out_file_path, mass_spectrum, output_type="excel"): 139 Thread.__init__(self) 140 141 self.output_file = Path(out_file_path) 142 143 # 'excel', 'csv' or 'pandas' 144 self.output_type = output_type 145 146 self.mass_spectrum = mass_spectrum 147 148 # collect all assigned atoms and order them accordingly to the Atoms.atoms_order list 149 self.atoms_order_list = self.get_all_used_atoms_in_order(self.mass_spectrum) 150 151 self._init_columns()
This constructor should always be called with keyword arguments. Arguments are:
group should be None; reserved for future extension when a ThreadGroup class is implemented.
target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.
name is the thread name. By default, a unique name is constructed of the form "Thread-N" where N is a small decimal number.
args is a list or tuple of arguments for the target invocation. Defaults to ().
kwargs is a dictionary of keyword arguments for the target invocation. Defaults to {}.
If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.
181 @property 182 def output_type(self): 183 """Returns the output type of the mass spectrum.""" 184 return self._output_type
Returns the output type of the mass spectrum.
197 def save(self): 198 """Save the mass spectrum data to the output file. 199 200 Raises 201 ------ 202 ValueError 203 If the output type is not supported. 204 """ 205 206 if self.output_type == "excel": 207 self.to_excel() 208 elif self.output_type == "csv": 209 self.to_csv() 210 elif self.output_type == "pandas": 211 self.to_pandas() 212 elif self.output_type == "parquet": 213 self.to_parquet() 214 elif self.output_type == "hdf5": 215 self.to_hdf() 216 else: 217 raise ValueError( 218 "Unkown output type: %s; it can be 'excel', 'csv', 'pandas', 'parquet' or 'hdf5'" 219 % self.output_type 220 )
Save the mass spectrum data to the output file.
Raises
- ValueError: If the output type is not supported.
222 def run(self): 223 """Run the export process. 224 225 This method is called when the thread starts. 226 It calls the save method to perform the export.""" 227 self.save()
Run the export process.
This method is called when the thread starts. It calls the save method to perform the export.
229 def get_pandas_df(self, additional_columns=None): 230 """Returns the mass spectrum data as a pandas DataFrame. 231 232 Parameters 233 ---------- 234 additional_columns : list, optional 235 Additional columns to include in the DataFrame. Defaults to None. 236 Suitable additional columns are: 'Aromaticity Index', 'NOSC', 'Aromaticity Index (modified)'. 237 238 Returns 239 ------- 240 DataFrame 241 The mass spectrum data as a pandas DataFrame. 242 """ 243 if additional_columns is not None: 244 possible_additional_columns = [ 245 "Aromaticity Index", 246 "NOSC", 247 "Aromaticity Index (modified)", 248 ] 249 if additional_columns: 250 for column in additional_columns: 251 if column not in possible_additional_columns: 252 raise ValueError("Invalid additional column: %s" % column) 253 columns = ( 254 self.columns_label 255 + additional_columns 256 + self.get_all_used_atoms_in_order(self.mass_spectrum) 257 ) 258 else: 259 columns = self.columns_label + self.get_all_used_atoms_in_order( 260 self.mass_spectrum 261 ) 262 dict_data_list = self.get_list_dict_data( 263 self.mass_spectrum, additional_columns=additional_columns 264 ) 265 df = DataFrame(dict_data_list, columns=columns) 266 df.attrs["name"] = self.output_file 267 return df
Returns the mass spectrum data as a pandas DataFrame.
Parameters
- additional_columns (list, optional): Additional columns to include in the DataFrame. Defaults to None. Suitable additional columns are: 'Aromaticity Index', 'NOSC', 'Aromaticity Index (modified)'.
Returns
- DataFrame: The mass spectrum data as a pandas DataFrame.
269 def write_settings(self, output_path, mass_spectrum): 270 """Writes the settings of the mass spectrum to a JSON file. 271 272 Parameters 273 ---------- 274 output_path : str 275 The output file path. 276 mass_spectrum : MassSpectrum 277 The mass spectrum to export. 278 """ 279 280 import json 281 282 dict_setting = parameter_to_dict.get_dict_data_ms(mass_spectrum) 283 284 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(mass_spectrum) 285 dict_setting["analyzer"] = mass_spectrum.analyzer 286 dict_setting["instrument_label"] = mass_spectrum.instrument_label 287 dict_setting["sample_name"] = mass_spectrum.sample_name 288 289 with open( 290 output_path.with_suffix(".json"), 291 "w", 292 encoding="utf8", 293 ) as outfile: 294 output = json.dumps( 295 dict_setting, sort_keys=True, indent=4, separators=(",", ": ") 296 ) 297 outfile.write(output)
Writes the settings of the mass spectrum to a JSON file.
Parameters
- output_path (str): The output file path.
- mass_spectrum (MassSpectrum): The mass spectrum to export.
299 def to_pandas(self, write_metadata=True): 300 """Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file. 301 302 Parameters 303 ---------- 304 write_metadata : bool, optional 305 Whether to write the metadata to a JSON file. Defaults to True. 306 """ 307 308 columns = self.columns_label + self.get_all_used_atoms_in_order( 309 self.mass_spectrum 310 ) 311 312 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 313 314 df = DataFrame(dict_data_list, columns=columns) 315 316 df.to_pickle(self.output_file.with_suffix(".pkl")) 317 318 if write_metadata: 319 self.write_settings(self.output_file, self.mass_spectrum)
Exports the mass spectrum data to a pandas DataFrame and saves it as a pickle file.
Parameters
- write_metadata (bool, optional): Whether to write the metadata to a JSON file. Defaults to True.
321 def to_excel(self, write_metadata=True): 322 """Exports the mass spectrum data to an Excel file. 323 324 Parameters 325 ---------- 326 write_metadata : bool, optional 327 Whether to write the metadata to a JSON file. Defaults to True. 328 """ 329 330 columns = self.columns_label + self.get_all_used_atoms_in_order( 331 self.mass_spectrum 332 ) 333 334 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 335 336 df = DataFrame(dict_data_list, columns=columns) 337 338 df.to_excel(self.output_file.with_suffix(".xlsx")) 339 340 if write_metadata: 341 self.write_settings(self.output_file, self.mass_spectrum)
Exports the mass spectrum data to an Excel file.
Parameters
- write_metadata (bool, optional): Whether to write the metadata to a JSON file. Defaults to True.
343 def to_csv(self, write_metadata=True): 344 """Exports the mass spectrum data to a CSV file. 345 346 Parameters 347 ---------- 348 write_metadata : bool, optional 349 Whether to write the metadata to a JSON file. Defaults to True. 350 """ 351 352 columns = self.columns_label + self.get_all_used_atoms_in_order( 353 self.mass_spectrum 354 ) 355 356 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 357 358 import csv 359 360 try: 361 with open(self.output_file.with_suffix(".csv"), "w", newline="") as csvfile: 362 writer = csv.DictWriter(csvfile, fieldnames=columns) 363 writer.writeheader() 364 for data in dict_data_list: 365 writer.writerow(data) 366 if write_metadata: 367 self.write_settings(self.output_file, self.mass_spectrum) 368 369 except IOError as ioerror: 370 print(ioerror)
Exports the mass spectrum data to a CSV file.
Parameters
- write_metadata (bool, optional): Whether to write the metadata to a JSON file. Defaults to True.
372 def to_parquet(self, write_metadata=True): 373 """Exports the mass spectrum data to a Parquet file. 374 375 Parameters 376 ---------- 377 write_metadata : bool, optional 378 Whether to write the metadata to a JSON file. Defaults to True. 379 380 Notes 381 ----- 382 Requires ``pyarrow`` (or ``fastparquet``) for pandas Parquet I/O. 383 """ 384 385 columns = self.columns_label + self.get_all_used_atoms_in_order( 386 self.mass_spectrum 387 ) 388 389 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 390 391 df = DataFrame(dict_data_list, columns=columns) 392 393 df.to_parquet(self.output_file.with_suffix(".parquet"), index=False) 394 395 if write_metadata: 396 self.write_settings(self.output_file, self.mass_spectrum)
Exports the mass spectrum data to a Parquet file.
Parameters
- write_metadata (bool, optional): Whether to write the metadata to a JSON file. Defaults to True.
Notes
Requires pyarrow (or fastparquet) for pandas Parquet I/O.
398 def to_json(self): 399 """Exports the mass spectrum data to a JSON string.""" 400 401 columns = self.columns_label + self.get_all_used_atoms_in_order( 402 self.mass_spectrum 403 ) 404 405 dict_data_list = self.get_list_dict_data(self.mass_spectrum) 406 407 df = DataFrame(dict_data_list, columns=columns) 408 409 # for key, values in dict_data.items(): 410 # if not values: dict_data[key] = NaN 411 412 # output = json.dumps(dict_data, sort_keys=True, indent=4, separators=(',', ': ')) 413 return df.to_json(orient="records")
Exports the mass spectrum data to a JSON string.
415 def add_mass_spectrum_to_hdf5( 416 self, 417 hdf_handle, 418 mass_spectrum, 419 group_key, 420 mass_spectra_group=None, 421 export_raw=True, 422 compression=DEFAULT_HDF5_COMPRESSION, 423 profile_dtype="f4", 424 ): 425 """Adds the mass spectrum data to an HDF5 file. 426 427 Parameters 428 ---------- 429 hdf_handle : h5py.File 430 The HDF5 file handle. 431 mass_spectrum : MassSpectrum 432 The mass spectrum to add to the HDF5 file. 433 group_key : str 434 The group key (where to add the mass spectrum data within the HDF5 file). 435 mass_spectra_group : h5py.Group, optional 436 The mass spectra group. Defaults to None (no group, mass spectrum is added to the root). 437 export_raw : bool, optional 438 Whether to export the raw data. Defaults to True. 439 If False, only the processed data (peaks) is exported (essentially centroided data). 440 compression : int, str or None, optional 441 Compression for the written datasets: a gzip level 0-9, ``"lzf"``, 442 or ``None``/``"none"``. Default is 4. ``"lzf"`` is h5py's own 443 filter, not a standard HDF5 one -- it needs h5py (or a tool 444 with the lzf plugin) to read the result back. 445 profile_dtype : str, optional 446 dtype used for the stored profile m/z/abundance arrays 447 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 448 """ 449 if mass_spectra_group is None: 450 # Check if the file has the necessary attributes and add them if not 451 # This assumes that if there is a mass_spectra_group, these attributes were already added to the file 452 if not hdf_handle.attrs.get("date_utc"): 453 timenow = str( 454 datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z") 455 ) 456 hdf_handle.attrs["date_utc"] = timenow 457 hdf_handle.attrs["file_name"] = mass_spectrum.filename.name 458 hdf_handle.attrs["data_structure"] = "mass_spectrum" 459 hdf_handle.attrs["analyzer"] = mass_spectrum.analyzer 460 hdf_handle.attrs["instrument_label"] = mass_spectrum.instrument_label 461 hdf_handle.attrs["sample_name"] = mass_spectrum.sample_name 462 463 list_results = self.list_dict_to_list(mass_spectrum, is_hdf5=True) 464 465 dict_ms_attrs = self.get_mass_spec_attrs(mass_spectrum) 466 467 setting_dicts = parameter_to_dict.get_dict_data_ms(mass_spectrum) 468 469 columns_labels = json.dumps( 470 self.columns_label + self.get_all_used_atoms_in_order(mass_spectrum), 471 sort_keys=False, 472 indent=4, 473 separators=(",", ": "), 474 ) 475 476 group_key = group_key 477 478 if mass_spectra_group is not None: 479 hdf_handle = mass_spectra_group 480 481 if group_key not in hdf_handle.keys(): 482 scan_group = hdf_handle.create_group(group_key) 483 484 # If there is raw data (from profile data) save it 485 if not mass_spectrum.is_centroid and export_raw: 486 # "mz" is always the experimental (uncalibrated) profile; a 487 # separate "mz_cal" row is added only when the spectrum is 488 # actually calibrated. mz_cal_profile alone isn't enough to 489 # tell: MzDomainCalibration.__init__ pre-populates it before 490 # any calibration has run, so a never-calibrated spectrum 491 # can still have a (stale/placeholder) mz_cal_profile. 492 row_labels = ["mz", "abundance"] 493 rows = [ 494 np.asarray(mass_spectrum._mz_exp), 495 np.asarray(mass_spectrum.abundance_profile), 496 ] 497 mz_cal_profile = mass_spectrum.mz_cal_profile 498 if mass_spectrum.is_calibrated and mz_cal_profile is not None: 499 row_labels.append("mz_cal") 500 rows.append(np.asarray(mz_cal_profile)) 501 502 mz_abun_array = np.array(rows, dtype=profile_dtype) 503 504 raw_ms_dataset = scan_group.create_dataset( 505 "raw_ms", 506 data=mz_abun_array, 507 dtype=profile_dtype, 508 **hdf5_dataset_kwargs(mz_abun_array.shape, compression=compression), 509 ) 510 raw_ms_dataset.attrs["row_labels"] = json.dumps(row_labels) 511 # Recorded independently of whether an "mz_cal" row was 512 # written, so a reader can trust this over row presence. 513 raw_ms_dataset.attrs["is_calibrated"] = bool( 514 mass_spectrum.is_calibrated 515 ) 516 517 # Add metadata to the raw_ms dataset when it exists 518 raw_ms_dataset.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 519 520 if isinstance(mass_spectrum, MassSpecfromFreq): 521 raw_ms_dataset.attrs["TransientSetting"] = json.dumps( 522 setting_dicts.get("TransientSetting"), 523 sort_keys=False, 524 indent=4, 525 separators=(",", ": "), 526 ) 527 else: 528 # For centroided data, store metadata directly on the scan group 529 scan_group.attrs["MassSpecAttrs"] = json.dumps(dict_ms_attrs) 530 531 if isinstance(mass_spectrum, MassSpecfromFreq): 532 scan_group.attrs["TransientSetting"] = json.dumps( 533 setting_dicts.get("TransientSetting"), 534 sort_keys=False, 535 indent=4, 536 separators=(",", ": "), 537 ) 538 539 else: 540 scan_group = hdf_handle.get(group_key) 541 542 # Next processed-data index: one past the highest existing numeric 543 # key, or 0 if there are none. "raw_ms" and "peak_profile_indices_*" 544 # are not processed-data indices (get_time_index_to_pull also only 545 # considers numeric keys, for the same reason). Using the max rather 546 # than a count matters for a legacy file that already has "raw_ms" 547 # and "1" (an old off-by-one: pre-fix code counted "raw_ms" as an 548 # occupied slot) -- counting numeric keys would give "1" again and 549 # collide with the existing one; max+1 gives "2". 550 numeric_keys = [int(k) for k in scan_group.keys() if k.isdigit()] 551 index_processed_data = str(max(numeric_keys) + 1) if numeric_keys else "0" 552 553 # Per-peak (left, apex, right) indices into the profile arrays above, 554 # in mspeaks order, so a reloaded centroid-with-profile mass 555 # spectrum doesn't need to re-derive peak boundaries from the peak 556 # table's row position (which isn't a profile index). Written once 557 # per peak-table version (same suffix as the processed-data index 558 # below), since the profile itself (raw_ms) doesn't change across 559 # reprocessing but the picked peaks do -- except raw_ms itself is 560 # only ever written on the group's first creation (just above), so 561 # if *this* mass_spectrum's profile is a different length (e.g. a 562 # later reprocessing that re-picked over different raw data), these 563 # indices would point into a profile that was never stored. Skip 564 # writing them in that case; the reader falls back to a search. 565 existing_raw_ms = scan_group.get("raw_ms") 566 if ( 567 not mass_spectrum.is_centroid 568 and export_raw 569 and existing_raw_ms is not None 570 and existing_raw_ms.shape[-1] == len(mass_spectrum.abundance_profile) 571 ): 572 peak_indices = np.array( 573 [ 574 ( 575 mspeak.peak_left_index, 576 mspeak.peak_apex_index, 577 mspeak.peak_right_index, 578 ) 579 for mspeak in mass_spectrum.mspeaks 580 ], 581 dtype=np.int32, 582 ).reshape(-1, 3) 583 scan_group.create_dataset( 584 f"peak_profile_indices_{index_processed_data}", 585 data=peak_indices, 586 **hdf5_dataset_kwargs(peak_indices.shape, compression=compression), 587 ) 588 589 timenow = str(datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S %Z")) 590 591 # Convert list_results to numpy array and optimize data types 592 array_results = np.array(list_results) 593 594 # Apply data type optimization for numeric columns 595 if array_results.dtype == np.float64: 596 array_results = array_results.astype(np.float32) 597 elif array_results.dtype == np.int64: 598 array_results = array_results.astype(np.int32) 599 600 processed_dset = scan_group.create_dataset( 601 index_processed_data, 602 data=array_results, 603 **hdf5_dataset_kwargs(array_results.shape, compression=compression), 604 ) 605 606 processed_dset.attrs["date_utc"] = timenow 607 608 processed_dset.attrs["ColumnsLabels"] = columns_labels 609 610 processed_dset.attrs["MoleculaSearchSetting"] = json.dumps( 611 setting_dicts.get("MoleculaSearch"), 612 sort_keys=False, 613 indent=4, 614 separators=(",", ": "), 615 ) 616 617 processed_dset.attrs["MassSpecPeakSetting"] = json.dumps( 618 setting_dicts.get("MassSpecPeak"), 619 sort_keys=False, 620 indent=4, 621 separators=(",", ": "), 622 ) 623 624 processed_dset.attrs["MassSpectrumSetting"] = json.dumps( 625 setting_dicts.get("MassSpectrum"), 626 sort_keys=False, 627 indent=4, 628 separators=(",", ": "), 629 )
Adds the mass spectrum data to an HDF5 file.
Parameters
- hdf_handle (h5py.File): The HDF5 file handle.
- mass_spectrum (MassSpectrum): The mass spectrum to add to the HDF5 file.
- group_key (str): The group key (where to add the mass spectrum data within the HDF5 file).
- mass_spectra_group (h5py.Group, optional): The mass spectra group. Defaults to None (no group, mass spectrum is added to the root).
- export_raw (bool, optional): Whether to export the raw data. Defaults to True. If False, only the processed data (peaks) is exported (essentially centroided data).
- compression (int, str or None, optional):
Compression for the written datasets: a gzip level 0-9,
"lzf", orNone/"none". Default is 4."lzf"is h5py's own filter, not a standard HDF5 one -- it needs h5py (or a tool with the lzf plugin) to read the result back. - profile_dtype (str, optional):
dtype used for the stored profile m/z/abundance arrays
(
"f4"or"f8"). Default is"f4".
631 def to_hdf(self, compression=DEFAULT_HDF5_COMPRESSION, profile_dtype="f4"): 632 """Exports the mass spectrum data to an HDF5 file. 633 634 Parameters 635 ---------- 636 compression : int, str or None, optional 637 Compression for the written datasets: a gzip level 0-9, 638 ``"lzf"``, or ``None``/``"none"``. Default is 4. ``"lzf"`` is 639 h5py's own filter, not a standard HDF5 one -- it needs h5py (or 640 a tool with the lzf plugin) to read the result back. 641 profile_dtype : str, optional 642 dtype used for the stored profile m/z/abundance arrays 643 (``"f4"`` or ``"f8"``). Default is ``"f4"``. 644 """ 645 646 with h5py.File(self.output_file.with_suffix(".hdf5"), "a") as hdf_handle: 647 self.add_mass_spectrum_to_hdf5( 648 hdf_handle, 649 self.mass_spectrum, 650 str(self.mass_spectrum.scan_number), 651 compression=compression, 652 profile_dtype=profile_dtype, 653 )
Exports the mass spectrum data to an HDF5 file.
Parameters
- compression (int, str or None, optional):
Compression for the written datasets: a gzip level 0-9,
"lzf", orNone/"none". Default is 4."lzf"is h5py's own filter, not a standard HDF5 one -- it needs h5py (or a tool with the lzf plugin) to read the result back. - profile_dtype (str, optional):
dtype used for the stored profile m/z/abundance arrays
(
"f4"or"f8"). Default is"f4".
655 def parameters_to_toml(self): 656 """Converts the mass spectrum parameters to a TOML string. 657 658 Returns 659 ------- 660 str 661 The TOML string of the mass spectrum parameters. 662 """ 663 664 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 665 666 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 667 dict_setting["analyzer"] = self.mass_spectrum.analyzer 668 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 669 dict_setting["sample_name"] = self.mass_spectrum.sample_name 670 671 output = _dumps_toml(dict_setting) 672 673 return output
Converts the mass spectrum parameters to a TOML string.
Returns
- str: The TOML string of the mass spectrum parameters.
675 def parameters_to_json(self): 676 """Converts the mass spectrum parameters to a JSON string. 677 678 Returns 679 ------- 680 str 681 The JSON string of the mass spectrum parameters. 682 """ 683 684 dict_setting = parameter_to_dict.get_dict_data_ms(self.mass_spectrum) 685 686 dict_setting["MassSpecAttrs"] = self.get_mass_spec_attrs(self.mass_spectrum) 687 dict_setting["analyzer"] = self.mass_spectrum.analyzer 688 dict_setting["instrument_label"] = self.mass_spectrum.instrument_label 689 dict_setting["sample_name"] = self.mass_spectrum.sample_name 690 691 output = json.dumps(dict_setting) 692 693 return output
Converts the mass spectrum parameters to a JSON string.
Returns
- str: The JSON string of the mass spectrum parameters.
695 def get_mass_spec_attrs(self, mass_spectrum): 696 """Returns the mass spectrum attributes as a dictionary. 697 698 Parameters 699 ---------- 700 mass_spectrum : MassSpectrum 701 The mass spectrum to export. 702 703 Returns 704 ------- 705 dict 706 The mass spectrum attributes. 707 """ 708 709 dict_ms_attrs = {} 710 dict_ms_attrs["polarity"] = mass_spectrum.polarity 711 dict_ms_attrs["rt"] = mass_spectrum.retention_time 712 dict_ms_attrs["tic"] = mass_spectrum.tic 713 dict_ms_attrs["mobility_scan"] = mass_spectrum.mobility_scan 714 dict_ms_attrs["mobility_rt"] = mass_spectrum.mobility_rt 715 dict_ms_attrs["Aterm"] = mass_spectrum.Aterm 716 dict_ms_attrs["Bterm"] = mass_spectrum.Bterm 717 dict_ms_attrs["Cterm"] = mass_spectrum.Cterm 718 dict_ms_attrs["baseline_noise"] = mass_spectrum.baseline_noise 719 dict_ms_attrs["baseline_noise_std"] = mass_spectrum.baseline_noise_std 720 721 return dict_ms_attrs
Returns the mass spectrum attributes as a dictionary.
Parameters
- mass_spectrum (MassSpectrum): The mass spectrum to export.
Returns
- dict: The mass spectrum attributes.
723 def get_all_used_atoms_in_order(self, mass_spectrum): 724 """Returns the list of assigned atoms in the order specified by Atoms.atoms_order list. 725 726 Parameters 727 ---------- 728 mass_spectrum : MassSpectrum 729 The mass spectrum to export. 730 731 Returns 732 ------- 733 list 734 The list of assigned atoms in the order specified by Atoms.atoms_order list. 735 """ 736 737 atoms_in_order = Atoms.atoms_order 738 all_used_atoms = set() 739 if mass_spectrum: 740 for ms_peak in mass_spectrum: 741 if ms_peak: 742 for m_formula in ms_peak: 743 for atom in m_formula.atoms: 744 all_used_atoms.add(atom) 745 746 def sort_method(atom): 747 return [atoms_in_order.index(atom)] 748 749 return sorted(all_used_atoms, key=sort_method)
Returns the list of assigned atoms in the order specified by Atoms.atoms_order list.
Parameters
- mass_spectrum (MassSpectrum): The mass spectrum to export.
Returns
- list: The list of assigned atoms in the order specified by Atoms.atoms_order list.
751 def list_dict_to_list(self, mass_spectrum, is_hdf5=False): 752 """Returns the mass spectrum data as a list of dictionaries. 753 754 Parameters 755 ---------- 756 mass_spectrum : MassSpectrum 757 The mass spectrum to export. 758 is_hdf5 : bool, optional 759 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 760 761 Returns 762 ------- 763 list 764 The mass spectrum data as a list of dictionaries. 765 """ 766 767 column_labels = self.columns_label + self.get_all_used_atoms_in_order( 768 mass_spectrum 769 ) 770 771 dict_list = self.get_list_dict_data(mass_spectrum, is_hdf5=is_hdf5) 772 773 all_lines = [] 774 for dict_res in dict_list: 775 result_line = [NaN] * len(column_labels) 776 777 for label, value in dict_res.items(): 778 label_index = column_labels.index(label) 779 result_line[label_index] = value 780 781 all_lines.append(result_line) 782 783 return all_lines
Returns the mass spectrum data as a list of dictionaries.
Parameters
- mass_spectrum (MassSpectrum): The mass spectrum to export.
- is_hdf5 (bool, optional): Whether the mass spectrum is being exported to an HDF5 file. Defaults to False.
Returns
- list: The mass spectrum data as a list of dictionaries.
785 def get_list_dict_data( 786 self, 787 mass_spectrum, 788 include_no_match=True, 789 include_isotopologues=True, 790 isotopologue_inline=True, 791 no_match_inline=False, 792 is_hdf5=False, 793 additional_columns=None, 794 ): 795 """Returns the mass spectrum data as a list of dictionaries. 796 797 Parameters 798 ---------- 799 mass_spectrum : MassSpectrum 800 The mass spectrum to export. 801 include_no_match : bool, optional 802 Whether to include unassigned (no match) data. Defaults to True. 803 include_isotopologues : bool, optional 804 Whether to include isotopologues. Defaults to True. 805 isotopologue_inline : bool, optional 806 Whether to include isotopologues inline. Defaults to True. 807 no_match_inline : bool, optional 808 Whether to include unassigned (no match) data inline. Defaults to False. 809 is_hdf5 : bool, optional 810 Whether the mass spectrum is being exported to an HDF5 file. Defaults to False. 811 812 Returns 813 ------- 814 list 815 The mass spectrum data as a list of dictionaries. 816 """ 817 818 dict_data_list = [] 819 820 if is_hdf5: 821 encode = ".encode('utf-8')" 822 else: 823 encode = "" 824 825 def add_no_match_dict_data(index, ms_peak): 826 """ 827 Export dictionary of mspeak info for unassigned (no match) data 828 """ 829 dict_result = { 830 "Index": index, 831 "m/z": ms_peak._mz_exp, 832 "Calibrated m/z": ms_peak.mz_exp, 833 "Peak Height": ms_peak.abundance, 834 "Peak Area": ms_peak.area, 835 "Resolving Power": ms_peak.resolving_power, 836 "S/N": ms_peak.signal_to_noise, 837 # Unassigned: polarity only (±1); no formula charge exists yet 838 "Ion Charge": ms_peak.polarity, 839 "Heteroatom Class": eval("Labels.unassigned{}".format(encode)), 840 } 841 842 dict_data_list.append(dict_result) 843 844 def add_match_dict_data(index, ms_peak, mformula, additional_columns=None): 845 """ 846 Export dictionary of mspeak info for assigned (match) data 847 """ 848 formula_dict = mformula.to_dict() 849 850 dict_result = { 851 "Index": index, 852 "m/z": ms_peak._mz_exp, 853 "Calibrated m/z": ms_peak.mz_exp, 854 "Calculated m/z": mformula.mz_calc, 855 "Peak Height": ms_peak.abundance, 856 "Peak Area": ms_peak.area, 857 "Resolving Power": ms_peak.resolving_power, 858 "S/N": ms_peak.signal_to_noise, 859 # Assigned: formula charge state (may be multi-charge); not peak polarity 860 "Ion Charge": mformula.ion_charge, 861 "m/z Error (ppm)": mformula.mz_error, 862 "Confidence Score": mformula.confidence_score, 863 "Isotopologue Similarity": mformula.isotopologue_similarity, 864 "m/z Error Score": mformula.average_mz_error_score, 865 "DBE": mformula.dbe, 866 "Heteroatom Class": eval("mformula.class_label{}".format(encode)), 867 "H/C": mformula.H_C, 868 "O/C": mformula.O_C, 869 "Ion Type": eval("mformula.ion_type.lower(){}".format(encode)), 870 "Is Isotopologue": int(mformula.is_isotopologue), 871 "Molecular Formula": eval("mformula.string{}".format(encode)), 872 } 873 if additional_columns is not None: 874 possible_dict = { 875 "Aromaticity Index": mformula.A_I, 876 "NOSC": mformula.nosc, 877 "Aromaticity Index (modified)": mformula.A_I_mod, 878 } 879 for column in additional_columns: 880 dict_result[column] = possible_dict.get(column) 881 882 if mformula.adduct_atom: 883 dict_result["Adduct"] = eval("mformula.adduct_atom{}".format(encode)) 884 885 if mformula.is_isotopologue: 886 dict_result["Mono Isotopic Index"] = mformula.mspeak_index_mono_isotopic 887 888 if self.atoms_order_list is None: 889 atoms_order_list = self.get_all_used_atoms_in_order(mass_spectrum) 890 else: 891 atoms_order_list = self.atoms_order_list 892 893 for atom in atoms_order_list: 894 if atom in formula_dict.keys(): 895 dict_result[atom] = formula_dict.get(atom) 896 897 dict_data_list.append(dict_result) 898 899 score_methods = mass_spectrum.molecular_search_settings.score_methods 900 selected_score_method = ( 901 mass_spectrum.molecular_search_settings.output_score_method 902 ) 903 904 if selected_score_method in score_methods: 905 # temp set score method as the one chosen in the output 906 current_method = mass_spectrum.molecular_search_settings.score_method 907 mass_spectrum.molecular_search_settings.score_method = selected_score_method 908 909 for index, ms_peak in enumerate(mass_spectrum): 910 # print(ms_peak.mz_exp) 911 912 if ms_peak: 913 m_formula = ms_peak.best_molecular_formula_candidate 914 915 if m_formula: 916 if not m_formula.is_isotopologue: 917 add_match_dict_data( 918 index, 919 ms_peak, 920 m_formula, 921 additional_columns=additional_columns, 922 ) 923 924 for ( 925 iso_mspeak_index, 926 iso_mf_formula, 927 ) in m_formula.mspeak_mf_isotopologues_indexes: 928 iso_ms_peak = mass_spectrum[iso_mspeak_index] 929 add_match_dict_data( 930 iso_mspeak_index, 931 iso_ms_peak, 932 iso_mf_formula, 933 additional_columns=additional_columns, 934 ) 935 else: 936 if include_no_match and no_match_inline: 937 add_no_match_dict_data(index, ms_peak) 938 939 if include_no_match and not no_match_inline: 940 for index, ms_peak in enumerate(mass_spectrum): 941 if not ms_peak: 942 add_no_match_dict_data(index, ms_peak) 943 # reset score method as the one chosen in the output 944 mass_spectrum.molecular_search_settings.score_method = current_method 945 946 else: 947 for index, ms_peak in enumerate(mass_spectrum): 948 # check if there is a molecular formula candidate for the msPeak 949 950 if ms_peak: 951 # m_formula = ms_peak.molecular_formula_lowest_error 952 for m_formula in ms_peak: 953 if mass_spectrum.molecular_search_settings.output_min_score > 0: 954 if ( 955 m_formula.confidence_score 956 >= mass_spectrum.molecular_search_settings.output_min_score 957 ): 958 if m_formula.is_isotopologue: # isotopologues inline 959 if include_isotopologues and isotopologue_inline: 960 add_match_dict_data( 961 index, 962 ms_peak, 963 m_formula, 964 additional_columns=additional_columns, 965 ) 966 else: 967 add_match_dict_data( 968 index, 969 ms_peak, 970 m_formula, 971 additional_columns=additional_columns, 972 ) # add monoisotopic peak 973 974 # cutoff because of low score 975 else: 976 add_no_match_dict_data(index, ms_peak) 977 978 else: 979 if m_formula.is_isotopologue: # isotopologues inline 980 if include_isotopologues and isotopologue_inline: 981 add_match_dict_data( 982 index, 983 ms_peak, 984 m_formula, 985 additional_columns=additional_columns, 986 ) 987 else: 988 add_match_dict_data( 989 index, 990 ms_peak, 991 m_formula, 992 additional_columns=additional_columns, 993 ) # add monoisotopic peak 994 else: 995 # include not_match 996 if include_no_match and no_match_inline: 997 add_no_match_dict_data(index, ms_peak) 998 999 if include_isotopologues and not isotopologue_inline: 1000 for index, ms_peak in enumerate(mass_spectrum): 1001 for m_formula in ms_peak: 1002 if m_formula.is_isotopologue: 1003 if ( 1004 m_formula.confidence_score 1005 >= mass_spectrum.molecular_search_settings.output_min_score 1006 ): 1007 add_match_dict_data( 1008 index, 1009 ms_peak, 1010 m_formula, 1011 additional_columns=additional_columns, 1012 ) 1013 1014 if include_no_match and not no_match_inline: 1015 for index, ms_peak in enumerate(mass_spectrum): 1016 if not ms_peak: 1017 add_no_match_dict_data(index, ms_peak) 1018 1019 # remove duplicated add_match data possibly introduced on the output_score_filter step 1020 res = [] 1021 [res.append(x) for x in dict_data_list if x not in res] 1022 1023 return res
Returns the mass spectrum data as a list of dictionaries.
Parameters
- mass_spectrum (MassSpectrum): The mass spectrum to export.
- include_no_match (bool, optional): Whether to include unassigned (no match) data. Defaults to True.
- include_isotopologues (bool, optional): Whether to include isotopologues. Defaults to True.
- isotopologue_inline (bool, optional): Whether to include isotopologues inline. Defaults to True.
- no_match_inline (bool, optional): Whether to include unassigned (no match) data inline. Defaults to False.
- is_hdf5 (bool, optional): Whether the mass spectrum is being exported to an HDF5 file. Defaults to False.
Returns
- list: The mass spectrum data as a list of dictionaries.