masster 0.3.10__py3-none-any.whl → 0.3.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of masster might be problematic. Click here for more details.

masster/sample/load.py CHANGED
@@ -85,9 +85,9 @@ def load(
85
85
  filename = self.file_path
86
86
  filename = os.path.abspath(filename)
87
87
  if not os.path.exists(filename):
88
- raise FileNotFoundError(f"Filename not valid. Provide a valid file path.")
88
+ raise FileNotFoundError("Filename not valid. Provide a valid file path.")
89
89
  self.ondisk = ondisk
90
-
90
+
91
91
  # check if file is mzML
92
92
  if filename.lower().endswith(".mzml"):
93
93
  self._load_mzML(filename)
@@ -97,7 +97,7 @@ def load(
97
97
  self._load_raw(filename)
98
98
  elif filename.lower().endswith(".sample5"):
99
99
  self._load_sample5(filename)
100
- #elif filename.lower().endswith(".h5"):
100
+ # elif filename.lower().endswith(".h5"):
101
101
  # self._load_h5(filename)
102
102
  else:
103
103
  raise ValueError("File must be .mzML, .wiff, *.raw, or .sample5")
@@ -137,7 +137,7 @@ def _load_mzML(
137
137
  # check if filename exists
138
138
  if filename is None:
139
139
  raise ValueError("Filename must be provided.")
140
-
140
+
141
141
  filename = os.path.abspath(filename)
142
142
  # check if it exists
143
143
  if not os.path.exists(filename):
@@ -310,7 +310,7 @@ def _load_raw(
310
310
  - Initiates further analysis by invoking analyze_dda().
311
311
  """
312
312
  from alpharaw.thermo import ThermoRawData
313
-
313
+
314
314
  if not filename:
315
315
  raise ValueError("Filename must be provided.")
316
316
 
@@ -318,7 +318,7 @@ def _load_raw(
318
318
  # check if it exists
319
319
  if not os.path.exists(filename):
320
320
  raise FileNotFoundError(f"File {filename} not found.")
321
-
321
+
322
322
  raw_data = ThermoRawData(centroided=False)
323
323
  raw_data.keep_k_peaks_per_spec = self.parameters.max_points_per_spectrum
324
324
  # check thatupdat filename ends with .raw
@@ -470,10 +470,12 @@ def _load_wiff(
470
470
  try:
471
471
  # Use masster's own implementation first
472
472
  from masster.sample.sciex import SciexWiffData as MassterSciexWiffData
473
+
473
474
  SciexWiffDataClass = MassterSciexWiffData
474
475
  except ImportError:
475
476
  # Fallback to alpharaw if masster implementation fails
476
477
  from alpharaw.sciex import SciexWiffData as AlpharawSciexWiffData
478
+
477
479
  SciexWiffDataClass = AlpharawSciexWiffData
478
480
 
479
481
  if not filename:
@@ -483,7 +485,7 @@ def _load_wiff(
483
485
  # check if it exists
484
486
  if not os.path.exists(filename):
485
487
  raise FileNotFoundError(f"File {filename} not found.")
486
-
488
+
487
489
  raw_data = SciexWiffDataClass(centroided=False)
488
490
  raw_data.keep_k_peaks_per_spec = self.parameters.max_points_per_spectrum
489
491
 
@@ -911,7 +913,7 @@ def index_file(self):
911
913
  oms.MzMLFile().load(self.file_source, omsexp)
912
914
  self.file_obj = omsexp
913
915
  elif os.path.exists(self.file_source) and self.file_source.lower().endswith(".sample5"):
914
- # this is an old save, try to see if
916
+ # this is an old save, try to see if
915
917
  if os.path.exists(self.file_source.replace(".sample5", ".wiff")):
916
918
  self.set_source(self.file_source.replace(".sample5", ".wiff"))
917
919
  elif os.path.exists(self.file_source.replace(".sample5", ".raw")):
@@ -919,7 +921,9 @@ def index_file(self):
919
921
  elif os.path.exists(self.file_source.replace(".sample5", ".mzml")):
920
922
  self.set_source(self.file_source.replace(".sample5", ".mzml"))
921
923
  else:
922
- raise FileNotFoundError(f"File {self.file_source} not found. Did the path change? Consider running source().")
924
+ raise FileNotFoundError(
925
+ f"File {self.file_source} not found. Did the path change? Consider running source()."
926
+ )
923
927
  self.index_file()
924
928
  else:
925
929
  raise FileNotFoundError(f"File {self.file_source} not found. Did the path change? Consider running source().")
masster/sample/plot.py CHANGED
@@ -66,49 +66,51 @@ hv.extension("bokeh")
66
66
  def _is_notebook_environment():
67
67
  """
68
68
  Detect if code is running in a notebook environment (Jupyter, JupyterLab, or Marimo).
69
-
69
+
70
70
  Returns:
71
71
  bool: True if running in a notebook, False otherwise
72
72
  """
73
73
  try:
74
74
  # Check for Jupyter/JupyterLab
75
75
  from IPython import get_ipython
76
+
76
77
  if get_ipython() is not None:
77
78
  # Check if we're in a notebook context
78
79
  shell = get_ipython().__class__.__name__
79
- if shell in ['ZMQInteractiveShell', 'Shell']: # Jupyter notebook/lab
80
+ if shell in ["ZMQInteractiveShell", "Shell"]: # Jupyter notebook/lab
80
81
  return True
81
-
82
+
82
83
  # Check for Marimo
83
84
  import sys
84
- if 'marimo' in sys.modules:
85
+
86
+ if "marimo" in sys.modules:
85
87
  return True
86
-
88
+
87
89
  # Additional check for notebook environments
88
- if hasattr(__builtins__, '__IPYTHON__') or hasattr(__builtins__, '_ih'):
90
+ if hasattr(__builtins__, "__IPYTHON__") or hasattr(__builtins__, "_ih"):
89
91
  return True
90
-
92
+
91
93
  except ImportError:
92
94
  pass
93
-
95
+
94
96
  return False
95
97
 
96
98
 
97
99
  def _display_plot(plot_object, layout=None):
98
100
  """
99
101
  Display a plot object in the appropriate way based on the environment.
100
-
102
+
101
103
  Args:
102
104
  plot_object: The plot object to display (holoviews overlay, etc.)
103
105
  layout: Optional panel layout object
104
-
106
+
105
107
  Returns:
106
108
  The layout object if in notebook environment, None otherwise
107
109
  """
108
110
  if _is_notebook_environment():
109
111
  # Display inline in notebook
110
112
  try:
111
- # For Jupyter notebooks, just return the plot object -
113
+ # For Jupyter notebooks, just return the plot object -
112
114
  # holoviews will handle the display automatically
113
115
  return plot_object
114
116
  except Exception:
@@ -395,17 +397,18 @@ def plot_2d(
395
397
  # Configure marker and size behavior based on size parameter
396
398
  use_dynamic_sizing = size.lower() in ["dyn", "dynamic"]
397
399
  use_slider_sizing = size.lower() == "slider"
398
-
400
+
399
401
  def dynamic_sizing_hook(plot, element):
400
402
  """Hook to convert size-based markers to radius-based for dynamic behavior"""
401
403
  try:
402
- if use_dynamic_sizing and hasattr(plot, 'state') and hasattr(plot.state, 'renderers'):
404
+ if use_dynamic_sizing and hasattr(plot, "state") and hasattr(plot.state, "renderers"):
403
405
  from bokeh.models import Circle
406
+
404
407
  for renderer in plot.state.renderers:
405
- if hasattr(renderer, 'glyph'):
408
+ if hasattr(renderer, "glyph"):
406
409
  glyph = renderer.glyph
407
410
  # Check if it's a circle/scatter glyph that we can convert
408
- if hasattr(glyph, 'size') and marker_type == "circle":
411
+ if hasattr(glyph, "size") and marker_type == "circle":
409
412
  # Create a new Circle glyph with radius instead of size
410
413
  new_glyph = Circle(
411
414
  x=glyph.x,
@@ -420,7 +423,7 @@ def plot_2d(
420
423
  except Exception:
421
424
  # Silently fail and use regular sizing if hook doesn't work
422
425
  pass
423
-
426
+
424
427
  if use_dynamic_sizing:
425
428
  # Dynamic sizing: use coordinate-based sizing that scales with zoom
426
429
  marker_type = "circle"
@@ -446,7 +449,7 @@ def plot_2d(
446
449
  size_2 = markersize
447
450
  base_radius = None # Not used in static mode
448
451
  hooks = []
449
-
452
+
450
453
  color_1 = "forestgreen"
451
454
  color_2 = "darkorange"
452
455
  if filename is not None:
@@ -519,14 +522,16 @@ def plot_2d(
519
522
  # find features with ms2_scans not None and iso==0
520
523
  features_df = feats[feats["ms2_scans"].notnull()]
521
524
  # Create feature points with proper sizing method
522
- feature_hover_1 = HoverTool(tooltips=[
523
- ("rt", "@rt"),
524
- ("m/z", "@mz{0.0000}"),
525
- ("feature_uid", "@feature_uid"),
526
- ("inty", "@inty"),
527
- ("quality", "@quality"),
528
- ("rt_delta", "@rt_delta"),
529
- ])
525
+ feature_hover_1 = HoverTool(
526
+ tooltips=[
527
+ ("rt", "@rt"),
528
+ ("m/z", "@mz{0.0000}"),
529
+ ("feature_uid", "@feature_uid"),
530
+ ("inty", "@inty"),
531
+ ("quality", "@quality"),
532
+ ("rt_delta", "@rt_delta"),
533
+ ],
534
+ )
530
535
  feature_points_1 = hv.Points(
531
536
  features_df,
532
537
  kdims=["rt", "mz"],
@@ -549,14 +554,16 @@ def plot_2d(
549
554
  )
550
555
  # find features without MS2 data
551
556
  features_df = feats[feats["ms2_scans"].isnull()]
552
- feature_hover_2 = HoverTool(tooltips=[
553
- ("rt", "@rt"),
554
- ("m/z", "@mz{0.0000}"),
555
- ("feature_uid", "@feature_uid"),
556
- ("inty", "@inty"),
557
- ("quality", "@quality"),
558
- ("rt_delta", "@rt_delta"),
559
- ])
557
+ feature_hover_2 = HoverTool(
558
+ tooltips=[
559
+ ("rt", "@rt"),
560
+ ("m/z", "@mz{0.0000}"),
561
+ ("feature_uid", "@feature_uid"),
562
+ ("inty", "@inty"),
563
+ ("quality", "@quality"),
564
+ ("rt_delta", "@rt_delta"),
565
+ ],
566
+ )
560
567
  feature_points_2 = hv.Points(
561
568
  features_df,
562
569
  kdims=["rt", "mz"],
@@ -583,16 +590,18 @@ def plot_2d(
583
590
  # Convert to pandas for plotting compatibility
584
591
  if hasattr(features_df, "to_pandas"):
585
592
  features_df = features_df.to_pandas()
586
- feature_hover_iso = HoverTool(tooltips=[
587
- ("rt", "@rt"),
588
- ("m/z", "@mz{0.0000}"),
589
- ("feature_uid", "@feature_uid"),
590
- ("inty", "@inty"),
591
- ("quality", "@quality"),
592
- ("rt_delta", "@rt_delta"),
593
- ("iso", "@iso"),
594
- ("iso_of", "@iso_of"),
595
- ])
593
+ feature_hover_iso = HoverTool(
594
+ tooltips=[
595
+ ("rt", "@rt"),
596
+ ("m/z", "@mz{0.0000}"),
597
+ ("feature_uid", "@feature_uid"),
598
+ ("inty", "@inty"),
599
+ ("quality", "@quality"),
600
+ ("rt_delta", "@rt_delta"),
601
+ ("iso", "@iso"),
602
+ ("iso_of", "@iso_of"),
603
+ ],
604
+ )
596
605
  feature_points_iso = hv.Points(
597
606
  features_df,
598
607
  kdims=["rt", "mz"],
@@ -623,13 +632,15 @@ def plot_2d(
623
632
  if len(ms2_orphan) > 0:
624
633
  # pandalize
625
634
  ms2 = ms2_orphan.to_pandas()
626
- ms2_hover_3 = HoverTool(tooltips=[
627
- ("rt", "@rt"),
628
- ("prec_mz", "@prec_mz{0.0000}"),
629
- ("index", "@index"),
630
- ("inty_tot", "@inty_tot"),
631
- ("bl", "@bl"),
632
- ])
635
+ ms2_hover_3 = HoverTool(
636
+ tooltips=[
637
+ ("rt", "@rt"),
638
+ ("prec_mz", "@prec_mz{0.0000}"),
639
+ ("index", "@index"),
640
+ ("inty_tot", "@inty_tot"),
641
+ ("bl", "@bl"),
642
+ ],
643
+ )
633
644
  feature_points_3 = hv.Points(
634
645
  ms2,
635
646
  kdims=["rt", "prec_mz"],
@@ -648,13 +659,15 @@ def plot_2d(
648
659
  if len(ms2_linked) > 0:
649
660
  # pandalize
650
661
  ms2 = ms2_linked.to_pandas()
651
- ms2_hover_4 = HoverTool(tooltips=[
652
- ("rt", "@rt"),
653
- ("prec_mz", "@prec_mz{0.0000}"),
654
- ("index", "@index"),
655
- ("inty_tot", "@inty_tot"),
656
- ("bl", "@bl"),
657
- ])
662
+ ms2_hover_4 = HoverTool(
663
+ tooltips=[
664
+ ("rt", "@rt"),
665
+ ("prec_mz", "@prec_mz{0.0000}"),
666
+ ("index", "@index"),
667
+ ("inty_tot", "@inty_tot"),
668
+ ("bl", "@bl"),
669
+ ],
670
+ )
658
671
  feature_points_4 = hv.Points(
659
672
  ms2,
660
673
  kdims=["rt", "prec_mz"],
@@ -688,17 +701,17 @@ def plot_2d(
688
701
  # For slider functionality, we need to work with the feature points directly
689
702
  # and not nest DynamicMaps. We'll create the slider using param and panel.
690
703
  import param
691
- import panel as pn
692
-
704
+ import panel as on
705
+
693
706
  class MarkerSizeController(param.Parameterized):
694
707
  size_slider = param.Number(default=markersize, bounds=(1, 20), step=0.5)
695
-
708
+
696
709
  controller = MarkerSizeController()
697
-
710
+
698
711
  # Create a function that generates just the feature overlays with different sizes
699
712
  def create_feature_overlay(size_val):
700
713
  feature_overlay = None
701
-
714
+
702
715
  if feature_points_4 is not None:
703
716
  updated_points_4 = feature_points_4.opts(size=size_val)
704
717
  feature_overlay = updated_points_4 if feature_overlay is None else feature_overlay * updated_points_4
@@ -713,22 +726,24 @@ def plot_2d(
713
726
  feature_overlay = updated_points_2 if feature_overlay is None else feature_overlay * updated_points_2
714
727
  if feature_points_iso is not None:
715
728
  updated_points_iso = feature_points_iso.opts(size=size_val)
716
- feature_overlay = updated_points_iso if feature_overlay is None else feature_overlay * updated_points_iso
717
-
729
+ feature_overlay = (
730
+ updated_points_iso if feature_overlay is None else feature_overlay * updated_points_iso
731
+ )
732
+
718
733
  # Combine with the static raster background
719
734
  if feature_overlay is not None:
720
735
  combined_overlay = raster * feature_overlay
721
736
  else:
722
737
  combined_overlay = raster
723
-
738
+
724
739
  if title is not None:
725
740
  combined_overlay = combined_overlay.opts(title=title)
726
-
741
+
727
742
  return combined_overlay
728
-
743
+
729
744
  # Create a horizontal control widget on top of the plot
730
745
  # Create the slider widget with explicit visibility
731
- size_slider = pn.widgets.FloatSlider(
746
+ size_slider = on.widgets.FloatSlider(
732
747
  name="Marker Size",
733
748
  start=1.0,
734
749
  end=20.0,
@@ -737,19 +752,19 @@ def plot_2d(
737
752
  width=300,
738
753
  height=40,
739
754
  margin=(5, 5),
740
- show_value=True
755
+ show_value=True,
741
756
  )
742
-
757
+
743
758
  # Create the slider widget row with clear styling
744
- slider_widget = pn.Row(
745
- pn.pane.HTML("<b>Marker Size Control:</b>", width=150, height=40, margin=(5, 10)),
759
+ slider_widget = on.Row(
760
+ on.pane.HTML("<b>Marker Size Control:</b>", width=150, height=40, margin=(5, 10)),
746
761
  size_slider,
747
762
  height=60,
748
- margin=10
763
+ margin=10,
749
764
  )
750
-
765
+
751
766
  # Create slider widget
752
- size_slider = pn.widgets.FloatSlider(
767
+ size_slider = on.widgets.FloatSlider(
753
768
  name="Marker Size",
754
769
  start=1.0,
755
770
  end=20.0,
@@ -758,18 +773,18 @@ def plot_2d(
758
773
  width=300,
759
774
  height=40,
760
775
  margin=(5, 5),
761
- show_value=True
776
+ show_value=True,
762
777
  )
763
-
764
- slider_widget = pn.Row(
765
- pn.pane.HTML("<b>Marker Size:</b>", width=100, height=40, margin=(5, 10)),
778
+
779
+ slider_widget = on.Row(
780
+ on.pane.HTML("<b>Marker Size:</b>", width=100, height=40, margin=(5, 10)),
766
781
  size_slider,
767
782
  height=60,
768
- margin=10
783
+ margin=10,
769
784
  )
770
-
785
+
771
786
  # Simple reactive plot - slider mode doesn't use dynamic rasterization
772
- @pn.depends(size_slider.param.value)
787
+ @on.depends(size_slider.param.value)
773
788
  def reactive_plot(size_val):
774
789
  overlay = create_feature_overlay(float(size_val))
775
790
  # Apply static rasterization for slider mode
@@ -779,19 +794,19 @@ def plot_2d(
779
794
  aggregator=ds.count(),
780
795
  width=raster_max_px,
781
796
  height=raster_max_px,
782
- dynamic=False # Static raster for slider mode
797
+ dynamic=False, # Static raster for slider mode
783
798
  ).opts(
784
- cnorm='eq_hist',
785
- tools=['hover'],
799
+ cnorm="eq_hist",
800
+ tools=["hover"],
786
801
  width=width,
787
- height=height
802
+ height=height,
788
803
  )
789
804
  else:
790
805
  return overlay
791
-
806
+
792
807
  # Create layout
793
- layout = pn.Column(slider_widget, reactive_plot, sizing_mode='stretch_width')
794
-
808
+ layout = on.Column(slider_widget, reactive_plot, sizing_mode="stretch_width")
809
+
795
810
  return layout
796
811
  else:
797
812
  # Create a panel layout without slider
@@ -1081,18 +1096,20 @@ def plot_2d_oracle(
1081
1096
  feat_df = feats.copy()
1082
1097
  feat_df = feat_df[feat_df["id_level"] == 2]
1083
1098
 
1084
- oracle_hover_1 = HoverTool(tooltips=[
1085
- ("rt", "@rt"),
1086
- ("m/z", "@mz{0.0000}"),
1087
- ("feature_uid", "@feature_uid"),
1088
- ("id_level", "@id_level"),
1089
- ("id_class", "@id_class"),
1090
- ("id_label", "@id_label"),
1091
- ("id_ion", "@id_ion"),
1092
- ("id_evidence", "@id_evidence"),
1093
- ("score", "@score"),
1094
- ("score2", "@score2"),
1095
- ])
1099
+ oracle_hover_1 = HoverTool(
1100
+ tooltips=[
1101
+ ("rt", "@rt"),
1102
+ ("m/z", "@mz{0.0000}"),
1103
+ ("feature_uid", "@feature_uid"),
1104
+ ("id_level", "@id_level"),
1105
+ ("id_class", "@id_class"),
1106
+ ("id_label", "@id_label"),
1107
+ ("id_ion", "@id_ion"),
1108
+ ("id_evidence", "@id_evidence"),
1109
+ ("score", "@score"),
1110
+ ("score2", "@score2"),
1111
+ ],
1112
+ )
1096
1113
  feature_points_1 = hv.Points(
1097
1114
  feat_df,
1098
1115
  kdims=["rt", "mz"],
@@ -1122,15 +1139,17 @@ def plot_2d_oracle(
1122
1139
  feat_df = feats.copy()
1123
1140
  feat_df = feat_df[(feat_df["ms2_scans"].notnull()) & (feat_df["id_level"] == 1)]
1124
1141
  if len(feat_df) > 0:
1125
- oracle_hover_2 = HoverTool(tooltips=[
1126
- ("rt", "@rt"),
1127
- ("m/z", "@mz{0.0000}"),
1128
- ("feature_uid", "@feature_uid"),
1129
- ("id_level", "@id_level"),
1130
- ("id_label", "@id_label"),
1131
- ("id_ion", "@id_ion"),
1132
- ("id_class", "@id_class"),
1133
- ])
1142
+ oracle_hover_2 = HoverTool(
1143
+ tooltips=[
1144
+ ("rt", "@rt"),
1145
+ ("m/z", "@mz{0.0000}"),
1146
+ ("feature_uid", "@feature_uid"),
1147
+ ("id_level", "@id_level"),
1148
+ ("id_label", "@id_label"),
1149
+ ("id_ion", "@id_ion"),
1150
+ ("id_class", "@id_class"),
1151
+ ],
1152
+ )
1134
1153
  feature_points_2 = hv.Points(
1135
1154
  feat_df,
1136
1155
  kdims=["rt", "mz"],
@@ -1157,15 +1176,17 @@ def plot_2d_oracle(
1157
1176
  feat_df = feats.copy()
1158
1177
  feat_df = feat_df[(feat_df["ms2_scans"].isnull()) & (feat_df["id_level"] == 1)]
1159
1178
  if len(feat_df) > 0:
1160
- oracle_hover_3 = HoverTool(tooltips=[
1161
- ("rt", "@rt"),
1162
- ("m/z", "@mz{0.0000}"),
1163
- ("feature_uid", "@feature_uid"),
1164
- ("id_level", "@id_level"),
1165
- ("id_label", "@id_label"),
1166
- ("id_ion", "@id_ion"),
1167
- ("id_class", "@id_class"),
1168
- ])
1179
+ oracle_hover_3 = HoverTool(
1180
+ tooltips=[
1181
+ ("rt", "@rt"),
1182
+ ("m/z", "@mz{0.0000}"),
1183
+ ("feature_uid", "@feature_uid"),
1184
+ ("id_level", "@id_level"),
1185
+ ("id_label", "@id_label"),
1186
+ ("id_ion", "@id_ion"),
1187
+ ("id_class", "@id_class"),
1188
+ ],
1189
+ )
1169
1190
  feature_points_3 = hv.Points(
1170
1191
  feat_df,
1171
1192
  kdims=["rt", "mz"],
@@ -1192,12 +1213,14 @@ def plot_2d_oracle(
1192
1213
  feat_df = feats.copy()
1193
1214
  feat_df = feat_df[(feat_df["ms2_scans"].notnull()) & (feat_df["id_level"] < 1)]
1194
1215
  if len(feat_df) > 0:
1195
- oracle_hover_4 = HoverTool(tooltips=[
1196
- ("rt", "@rt"),
1197
- ("m/z", "@mz{0.0000}"),
1198
- ("feature_uid", "@feature_uid"),
1199
- ("inty", "@inty"),
1200
- ])
1216
+ oracle_hover_4 = HoverTool(
1217
+ tooltips=[
1218
+ ("rt", "@rt"),
1219
+ ("m/z", "@mz{0.0000}"),
1220
+ ("feature_uid", "@feature_uid"),
1221
+ ("inty", "@inty"),
1222
+ ],
1223
+ )
1201
1224
  feature_points_4 = hv.Points(
1202
1225
  feat_df,
1203
1226
  kdims=["rt", "mz"],
@@ -1216,12 +1239,14 @@ def plot_2d_oracle(
1216
1239
  feat_df = feats.copy()
1217
1240
  feat_df = feat_df[(feat_df["ms2_scans"].isnull()) & (feat_df["id_level"] < 1)]
1218
1241
  if len(feat_df) > 0:
1219
- oracle_hover_5 = HoverTool(tooltips=[
1220
- ("rt", "@rt"),
1221
- ("m/z", "@mz{0.0000}"),
1222
- ("feature_uid", "@feature_uid"),
1223
- ("inty", "@inty"),
1224
- ])
1242
+ oracle_hover_5 = HoverTool(
1243
+ tooltips=[
1244
+ ("rt", "@rt"),
1245
+ ("m/z", "@mz{0.0000}"),
1246
+ ("feature_uid", "@feature_uid"),
1247
+ ("inty", "@inty"),
1248
+ ],
1249
+ )
1225
1250
  feature_points_5 = hv.Points(
1226
1251
  feat_df,
1227
1252
  kdims=["rt", "mz"],
@@ -519,6 +519,10 @@ def find_features(self, **kwargs):
519
519
  low-quality peaks), lower values make it more permissive. Typical tuning range: ~3 (relaxed) to >10
520
520
  (stringent). Default: 10.0.
521
521
 
522
+ - isotope_filtering_model (str):
523
+ Isotope filtering model ('metabolites (2% RMS)', 'metabolites (5% RMS)', 'peptides', 'none').
524
+ Default: 'metabolites (5% RMS)'.
525
+
522
526
  Tuning recommendation: first set ``chrom_fwhm`` to match your LC peak shape, then set ``noise`` to a baseline
523
527
  intensity filter for your data, and finally adjust ``chrom_peak_snr`` to reach the desired balance between
524
528
  sensitivity and specificity.
@@ -556,24 +560,25 @@ def find_features(self, **kwargs):
556
560
  self.logger.warning(f"Unknown parameter {key} ignored")
557
561
 
558
562
  # Set global parameters
559
- if hasattr(params, 'threads') and params.threads is not None:
563
+ if hasattr(params, "threads") and params.threads is not None:
560
564
  try:
561
565
  # Try setting via OpenMP environment variable first (newer approach)
562
566
  import os
563
- os.environ['OMP_NUM_THREADS'] = str(params.threads)
567
+
568
+ os.environ["OMP_NUM_THREADS"] = str(params.threads)
564
569
  self.logger.debug(f"Set thread count to {params.threads} via OMP_NUM_THREADS")
565
570
  except Exception:
566
571
  self.logger.warning(f"Could not set thread count to {params.threads} - using default")
567
-
572
+
568
573
  # Set debug mode if enabled
569
- if hasattr(params, 'debug') and params.debug:
574
+ if hasattr(params, "debug") and params.debug:
570
575
  self.logger.debug("Debug mode enabled")
571
- elif hasattr(params, 'no_progress') and params.no_progress:
576
+ elif hasattr(params, "no_progress") and params.no_progress:
572
577
  self.logger.debug("No progress mode enabled")
573
-
578
+
574
579
  self.logger.info("Starting feature detection...")
575
580
  self.logger.debug(
576
- f"Parameters: chrom_fwhm={params.get('chrom_fwhm')}, noise={params.get('noise')}, tol_ppm={params.get('tol_ppm')}",
581
+ f"Parameters: chrom_fwhm={params.get('chrom_fwhm')}, noise={params.get('noise')}, tol_ppm={params.get('tol_ppm')}, isotope_filtering_model={params.get('isotope_filtering_model')}",
577
582
  )
578
583
 
579
584
  exp = oms.MSExperiment()
@@ -602,7 +607,8 @@ def find_features(self, **kwargs):
602
607
  # Apply MTD parameters
603
608
  mtd_par.setValue("mass_error_ppm", float(params.get("tol_ppm")))
604
609
  mtd_par.setValue("noise_threshold_int", float(params.get("noise")))
605
- mtd_par.setValue("min_trace_length",
610
+ mtd_par.setValue(
611
+ "min_trace_length",
606
612
  float(params.get("min_trace_length_multiplier")) * float(params.get("chrom_fwhm_min")),
607
613
  )
608
614
  mtd_par.setValue(
@@ -610,7 +616,7 @@ def find_features(self, **kwargs):
610
616
  int(params.get("trace_termination_outliers")),
611
617
  )
612
618
  mtd_par.setValue("chrom_peak_snr", float(params.get("chrom_peak_snr")))
613
-
619
+
614
620
  # Additional MTD parameters
615
621
  mtd_par.setValue("min_sample_rate", float(params.get("min_sample_rate")))
616
622
  mtd_par.setValue("min_trace_length", float(params.get("min_trace_length")))
@@ -636,10 +642,10 @@ def find_features(self, **kwargs):
636
642
  epd_par.setValue("masstrace_snr_filtering", "true")
637
643
  if params.get("mz_scoring_13C"):
638
644
  epd_par.setValue("mz_scoring_13C", "true")
639
-
645
+
640
646
  # Additional EPD parameters
641
647
  epd_par.setValue("enabled", "true" if params.get("enabled") else "false")
642
-
648
+
643
649
  epd.setParameters(epd_par)
644
650
  epd.detectPeaks(mass_traces, mass_traces_deconvol)
645
651
 
@@ -675,7 +681,7 @@ def find_features(self, **kwargs):
675
681
  ffm_par.setValue("local_mz_range", float(params.get("local_mz_range")))
676
682
  ffm_par.setValue("charge_lower_bound", int(params.get("charge_lower_bound")))
677
683
  ffm_par.setValue("charge_upper_bound", int(params.get("charge_upper_bound")))
678
-
684
+ ffm_par.setValue("isotope_filtering_model", params.get("isotope_filtering_model"))
679
685
 
680
686
  ffm.setParameters(ffm_par)
681
687