masster 0.4.0__py3-none-any.whl → 0.4.1__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.
Files changed (54) hide show
  1. masster/__init__.py +8 -8
  2. masster/_version.py +1 -1
  3. masster/chromatogram.py +3 -9
  4. masster/data/libs/README.md +1 -1
  5. masster/data/libs/ccm.csv +120 -120
  6. masster/data/libs/ccm.py +116 -62
  7. masster/data/libs/central_carbon_README.md +1 -1
  8. masster/data/libs/urine.py +161 -65
  9. masster/data/libs/urine_metabolites.csv +4693 -4693
  10. masster/data/wiff/2025_01_14_VW_7600_LpMx_DBS_CID_2min_TOP15_030msecMS1_005msecReac_CE35_DBS-ON_3.mzML +2 -2
  11. masster/logger.py +43 -78
  12. masster/sample/__init__.py +1 -1
  13. masster/sample/adducts.py +264 -338
  14. masster/sample/defaults/find_adducts_def.py +8 -21
  15. masster/sample/defaults/find_features_def.py +1 -6
  16. masster/sample/defaults/get_spectrum_def.py +1 -5
  17. masster/sample/defaults/sample_def.py +1 -5
  18. masster/sample/h5.py +282 -561
  19. masster/sample/helpers.py +75 -131
  20. masster/sample/lib.py +17 -42
  21. masster/sample/load.py +17 -31
  22. masster/sample/parameters.py +2 -6
  23. masster/sample/plot.py +27 -88
  24. masster/sample/processing.py +87 -117
  25. masster/sample/quant.py +51 -57
  26. masster/sample/sample.py +90 -103
  27. masster/sample/sample5_schema.json +44 -44
  28. masster/sample/save.py +12 -35
  29. masster/sample/sciex.py +19 -66
  30. masster/spectrum.py +20 -58
  31. masster/study/__init__.py +1 -1
  32. masster/study/defaults/align_def.py +1 -5
  33. masster/study/defaults/fill_chrom_def.py +1 -5
  34. masster/study/defaults/fill_def.py +1 -5
  35. masster/study/defaults/integrate_chrom_def.py +1 -5
  36. masster/study/defaults/integrate_def.py +1 -5
  37. masster/study/defaults/study_def.py +25 -58
  38. masster/study/export.py +207 -233
  39. masster/study/h5.py +136 -470
  40. masster/study/helpers.py +202 -495
  41. masster/study/helpers_optimized.py +13 -40
  42. masster/study/id.py +110 -213
  43. masster/study/load.py +143 -230
  44. masster/study/plot.py +257 -518
  45. masster/study/processing.py +257 -469
  46. masster/study/save.py +5 -15
  47. masster/study/study.py +276 -379
  48. masster/study/study5_schema.json +96 -96
  49. {masster-0.4.0.dist-info → masster-0.4.1.dist-info}/METADATA +1 -1
  50. masster-0.4.1.dist-info/RECORD +67 -0
  51. masster-0.4.0.dist-info/RECORD +0 -67
  52. {masster-0.4.0.dist-info → masster-0.4.1.dist-info}/WHEEL +0 -0
  53. {masster-0.4.0.dist-info → masster-0.4.1.dist-info}/entry_points.txt +0 -0
  54. {masster-0.4.0.dist-info → masster-0.4.1.dist-info}/licenses/LICENSE +0 -0
masster/sample/load.py CHANGED
@@ -47,10 +47,10 @@ import pyopenms as oms
47
47
 
48
48
  from tqdm import tqdm
49
49
 
50
- from master.chromatogram import Chromatogram
50
+ from masster.chromatogram import Chromatogram
51
51
 
52
52
  # Parameters removed - using hardcoded defaults
53
- from master.spectrum import Spectrum
53
+ from masster.spectrum import Spectrum
54
54
 
55
55
 
56
56
  def load(
@@ -177,7 +177,7 @@ def load_study(
177
177
  ):
178
178
  """
179
179
  Backward compatibility alias for load_noms1().
180
-
180
+
181
181
  This method is deprecated. Use load_noms1() instead.
182
182
  """
183
183
  return self.load_noms1(filename=filename, ondisk=ondisk, type=type, label=label)
@@ -263,16 +263,12 @@ def _load_mzML(
263
263
  energy = None
264
264
  else:
265
265
  prec_mz = s.getPrecursors()[0].getMZ()
266
- precursorIsolationWindowLowerMZ = s.getPrecursors()[
267
- 0
268
- ].getIsolationWindowLowerOffset()
269
- precursorIsolationWindowUpperMZ = s.getPrecursors()[
270
- 0
271
- ].getIsolationWindowUpperOffset()
266
+ precursorIsolationWindowLowerMZ = s.getPrecursors()[0].getIsolationWindowLowerOffset()
267
+ precursorIsolationWindowUpperMZ = s.getPrecursors()[0].getIsolationWindowUpperOffset()
272
268
  prec_intyensity = s.getPrecursors()[0].getIntensity()
273
269
  # Try to get collision energy from meta values first, fallback to getActivationEnergy()
274
270
  try:
275
- energy = s.getPrecursors()[0].getMetaValue("collision energy")
271
+ energy = s.getPrecursors()[0].getMetaValue('collision energy')
276
272
  if energy is None or energy == 0.0:
277
273
  energy = s.getPrecursors()[0].getActivationEnergy()
278
274
  except Exception:
@@ -556,12 +552,12 @@ def _load_wiff(
556
552
  filename=None,
557
553
  ):
558
554
  try:
559
- # Use master's own implementation first
560
- from master.sample.sciex import SciexWiffData as MasterSciexWiffData
555
+ # Use masster's own implementation first
556
+ from masster.sample.sciex import SciexWiffData as MassterSciexWiffData
561
557
 
562
- SciexWiffDataClass = MasterSciexWiffData
558
+ SciexWiffDataClass = MassterSciexWiffData
563
559
  except ImportError:
564
- # Fallback to alpharaw if master implementation fails
560
+ # Fallback to alpharaw if masster implementation fails
565
561
  from alpharaw.sciex import SciexWiffData as AlpharawSciexWiffData
566
562
 
567
563
  SciexWiffDataClass = AlpharawSciexWiffData
@@ -972,8 +968,8 @@ def index_file(self):
972
968
  try:
973
969
  from alpharaw.sciex import SciexWiffData
974
970
  except ImportError:
975
- # Fallback to master's own implementation
976
- from master.sample.sciex import SciexWiffData
971
+ # Fallback to masster's own implementation
972
+ from masster.sample.sciex import SciexWiffData
977
973
 
978
974
  raw_data = SciexWiffData(centroided=False)
979
975
  raw_data.keep_k_peaks_per_spec = self.parameters.max_points_per_spectrum
@@ -989,9 +985,7 @@ def index_file(self):
989
985
  self.logger.info("Index raw data...")
990
986
  raw_data.import_raw(self.file_source)
991
987
  self.file_obj = raw_data
992
- elif os.path.exists(self.file_source) and self.file_source.lower().endswith(
993
- ".mzml",
994
- ):
988
+ elif os.path.exists(self.file_source) and self.file_source.lower().endswith(".mzml"):
995
989
  self.file_interface = "oms"
996
990
  omsexp: oms.OnDiscMSExperiment | oms.MSExperiment
997
991
  if self.ondisk:
@@ -1001,9 +995,7 @@ def index_file(self):
1001
995
  omsexp = oms.MSExperiment()
1002
996
  oms.MzMLFile().load(self.file_source, omsexp)
1003
997
  self.file_obj = omsexp
1004
- elif os.path.exists(self.file_source) and self.file_source.lower().endswith(
1005
- ".sample5",
1006
- ):
998
+ elif os.path.exists(self.file_source) and self.file_source.lower().endswith(".sample5"):
1007
999
  # this is an old save, try to see if
1008
1000
  if os.path.exists(self.file_source.replace(".sample5", ".wiff")):
1009
1001
  self.set_source(self.file_source.replace(".sample5", ".wiff"))
@@ -1017,9 +1009,7 @@ def index_file(self):
1017
1009
  )
1018
1010
  self.index_file()
1019
1011
  else:
1020
- raise FileNotFoundError(
1021
- f"File {self.file_source} not found. Did the path change? Consider running source().",
1022
- )
1012
+ raise FileNotFoundError(f"File {self.file_source} not found. Did the path change? Consider running source().")
1023
1013
 
1024
1014
 
1025
1015
  def _load_ms2data(
@@ -1224,9 +1214,7 @@ def chrom_extract(
1224
1214
  scan_uid = trace["scan_uid"]
1225
1215
  # find all ms1 data with scan_uid and mz between q1-mz_tol and q1+mz_tol
1226
1216
  d = self.ms1_df.filter(
1227
- (pl.col("scan_uid").is_in(scan_uid))
1228
- & (pl.col("mz") >= q1 - mz_tol)
1229
- & (pl.col("mz") <= q1 + mz_tol),
1217
+ (pl.col("scan_uid").is_in(scan_uid)) & (pl.col("mz") >= q1 - mz_tol) & (pl.col("mz") <= q1 + mz_tol),
1230
1218
  )
1231
1219
  # for all unique rt values, find the maximum inty
1232
1220
  eic_rt = d.group_by("rt").agg(pl.col("inty").max())
@@ -1245,9 +1233,7 @@ def chrom_extract(
1245
1233
  scan_uid = trace["scan_uid"]
1246
1234
  # find all ms2 data with scan_uid and mz between q3-mz_tol and q3+mz_tol
1247
1235
  d = self.ms2data.filter(
1248
- (pl.col("scan_uid").is_in(scan_uid))
1249
- & (pl.col("mz") >= q3 - mz_tol)
1250
- & (pl.col("mz") <= q3 + mz_tol),
1236
+ (pl.col("scan_uid").is_in(scan_uid)) & (pl.col("mz") >= q3 - mz_tol) & (pl.col("mz") <= q3 + mz_tol),
1251
1237
  )
1252
1238
  # for all unique rt values, find the maximum inty
1253
1239
  eic_rt = d.group_by("rt").agg(pl.col("inty").max())
@@ -53,11 +53,7 @@ def get_parameters(self, keys):
53
53
  if keys[0] == "sample":
54
54
  if len(keys) == 1:
55
55
  # Return the whole sample_defaults object as dict
56
- return (
57
- self.parameters.to_dict()
58
- if hasattr(self.parameters, "to_dict")
59
- else None
60
- )
56
+ return self.parameters.to_dict() if hasattr(self.parameters, "to_dict") else None
61
57
  else:
62
58
  # Get specific parameter from sample_defaults object
63
59
  param_name = keys[1]
@@ -89,7 +85,7 @@ def update_parameters(self, **kwargs):
89
85
  - Individual parameter names and values (see sample_defaults for details)
90
86
  """
91
87
  # Import here to avoid circular imports
92
- from master.sample.defaults.sample_def import (
88
+ from masster.sample.defaults.sample_def import (
93
89
  sample_defaults as SampleDefaults,
94
90
  )
95
91
 
masster/sample/plot.py CHANGED
@@ -144,7 +144,7 @@ def _display_plot(plot_object, layout=None):
144
144
  def _handle_sample_plot_output(self, plot_obj, filename=None, plot_type="bokeh"):
145
145
  """
146
146
  Helper function to handle consistent save/display behavior for sample plots.
147
-
147
+
148
148
  Parameters:
149
149
  plot_obj: The plot object (bokeh figure, holoviews layout, or panel object)
150
150
  filename: Optional filename to save the plot
@@ -153,24 +153,21 @@ def _handle_sample_plot_output(self, plot_obj, filename=None, plot_type="bokeh")
153
153
  if filename is not None:
154
154
  # Convert relative paths to absolute paths using sample folder as base
155
155
  import os
156
-
157
- if hasattr(self, "folder") and self.folder and not os.path.isabs(filename):
156
+ if hasattr(self, 'folder') and self.folder and not os.path.isabs(filename):
158
157
  filename = os.path.join(self.folder, filename)
159
-
158
+
160
159
  # Convert to absolute path for logging
161
160
  abs_filename = os.path.abspath(filename)
162
-
161
+
163
162
  if filename.endswith(".html"):
164
163
  if plot_type == "panel":
165
164
  plot_obj.save(filename, embed=True) # type: ignore[attr-defined]
166
165
  elif plot_type == "holoviews":
167
166
  import panel
168
-
169
167
  panel.panel(plot_obj).save(filename, embed=True) # type: ignore[attr-defined]
170
168
  elif plot_type == "bokeh":
171
169
  from bokeh.plotting import output_file
172
170
  from bokeh.io import save
173
-
174
171
  output_file(filename)
175
172
  save(plot_obj)
176
173
  self.logger.info(f"Plot saved to: {abs_filename}")
@@ -178,75 +175,61 @@ def _handle_sample_plot_output(self, plot_obj, filename=None, plot_type="bokeh")
178
175
  try:
179
176
  if plot_type == "bokeh":
180
177
  from bokeh.io.export import export_png
181
-
182
178
  export_png(plot_obj, filename=filename)
183
179
  elif plot_type in ["panel", "holoviews"]:
184
180
  import holoviews as hv
185
-
186
181
  hv.save(plot_obj, filename, fmt="png")
187
182
  self.logger.info(f"Plot saved to: {abs_filename}")
188
183
  except Exception:
189
184
  # Fall back to HTML if PNG export not available
190
- html_filename = filename.replace(".png", ".html")
185
+ html_filename = filename.replace('.png', '.html')
191
186
  abs_html_filename = os.path.abspath(html_filename)
192
187
  if plot_type == "panel":
193
188
  plot_obj.save(html_filename, embed=True) # type: ignore[attr-defined]
194
189
  elif plot_type == "holoviews":
195
190
  import panel
196
-
197
191
  panel.panel(plot_obj).save(html_filename, embed=True) # type: ignore[attr-defined]
198
192
  elif plot_type == "bokeh":
199
193
  from bokeh.plotting import output_file
200
194
  from bokeh.io import save
201
-
202
195
  output_file(html_filename)
203
196
  save(plot_obj)
204
- self.logger.warning(
205
- f"PNG export not available, saved as HTML instead: {abs_html_filename}",
206
- )
197
+ self.logger.warning(f"PNG export not available, saved as HTML instead: {abs_html_filename}")
207
198
  elif filename.endswith(".pdf"):
208
199
  # Try to save as PDF, fall back to HTML if not available
209
200
  try:
210
201
  if plot_type == "bokeh":
211
202
  from bokeh.io.export import export_pdf
212
-
213
203
  export_pdf(plot_obj, filename=filename)
214
204
  elif plot_type in ["panel", "holoviews"]:
215
205
  import holoviews as hv
216
-
217
206
  hv.save(plot_obj, filename, fmt="pdf")
218
207
  self.logger.info(f"Plot saved to: {abs_filename}")
219
208
  except ImportError:
220
209
  # Fall back to HTML if PDF export not available
221
- html_filename = filename.replace(".pdf", ".html")
210
+ html_filename = filename.replace('.pdf', '.html')
222
211
  abs_html_filename = os.path.abspath(html_filename)
223
212
  if plot_type == "panel":
224
213
  plot_obj.save(html_filename, embed=True) # type: ignore[attr-defined]
225
214
  elif plot_type == "holoviews":
226
215
  import panel
227
-
228
216
  panel.panel(plot_obj).save(html_filename, embed=True) # type: ignore[attr-defined]
229
217
  elif plot_type == "bokeh":
230
218
  from bokeh.plotting import output_file
231
219
  from bokeh.io import save
232
-
233
220
  output_file(html_filename)
234
221
  save(plot_obj)
235
- self.logger.warning(
236
- f"PDF export not available, saved as HTML instead: {abs_html_filename}",
237
- )
222
+ self.logger.warning(f"PDF export not available, saved as HTML instead: {abs_html_filename}")
238
223
  else:
239
224
  # Default to HTML for unknown extensions
240
225
  if plot_type == "panel":
241
226
  plot_obj.save(filename, embed=True) # type: ignore[attr-defined]
242
227
  elif plot_type == "holoviews":
243
228
  import panel
244
-
245
229
  panel.panel(plot_obj).save(filename, embed=True) # type: ignore[attr-defined]
246
230
  elif plot_type == "bokeh":
247
231
  from bokeh.plotting import output_file
248
232
  from bokeh.io import save
249
-
250
233
  output_file(filename)
251
234
  save(plot_obj)
252
235
  self.logger.info(f"Plot saved to: {abs_filename}")
@@ -256,11 +239,9 @@ def _handle_sample_plot_output(self, plot_obj, filename=None, plot_type="bokeh")
256
239
  plot_obj.show() # type: ignore[attr-defined]
257
240
  elif plot_type == "holoviews":
258
241
  import panel
259
-
260
242
  return panel.panel(plot_obj)
261
243
  elif plot_type == "bokeh":
262
244
  from bokeh.plotting import show
263
-
264
245
  show(plot_obj)
265
246
 
266
247
 
@@ -318,9 +299,7 @@ def plot_chrom(
318
299
  if isinstance(feature_uids, int):
319
300
  feature_uids = [feature_uids]
320
301
  # select only the features with feature_uid in feature_uids
321
- feats = self.features_df[
322
- self.features_df["feature_uid"].is_in(feature_uids)
323
- ].clone()
302
+ feats = self.features_df[self.features_df["feature_uid"].is_in(feature_uids)].clone()
324
303
 
325
304
  # make sure feature_uid is a list of integers
326
305
 
@@ -396,7 +375,7 @@ def plot_chrom(
396
375
 
397
376
  layout = layout.cols(1)
398
377
  layout = panel.Column(layout)
399
-
378
+
400
379
  # Use consistent save/display behavior
401
380
  self._handle_sample_plot_output(layout, filename, "panel")
402
381
 
@@ -494,13 +473,9 @@ def plot_2d(
494
473
  # keep only rt, mz, and inty
495
474
  spectradf = spectradf.select(["rt", "mz", "inty"])
496
475
  if mz_range is not None:
497
- spectradf = spectradf.filter(
498
- (pl.col("mz") >= mz_range[0]) & (pl.col("mz") <= mz_range[1]),
499
- )
476
+ spectradf = spectradf.filter((pl.col("mz") >= mz_range[0]) & (pl.col("mz") <= mz_range[1]))
500
477
  if rt_range is not None:
501
- spectradf = spectradf.filter(
502
- (pl.col("rt") >= rt_range[0]) & (pl.col("rt") <= rt_range[1]),
503
- )
478
+ spectradf = spectradf.filter((pl.col("rt") >= rt_range[0]) & (pl.col("rt") <= rt_range[1]))
504
479
  maxrt = spectradf["rt"].max()
505
480
  minrt = spectradf["rt"].min()
506
481
  maxmz = spectradf["mz"].max()
@@ -532,11 +507,7 @@ def plot_2d(
532
507
  def dynamic_sizing_hook(plot, element):
533
508
  """Hook to convert size-based markers to radius-based for dynamic behavior"""
534
509
  try:
535
- if (
536
- use_dynamic_sizing
537
- and hasattr(plot, "state")
538
- and hasattr(plot.state, "renderers")
539
- ):
510
+ if use_dynamic_sizing and hasattr(plot, "state") and hasattr(plot.state, "renderers"):
540
511
  from bokeh.models import Circle
541
512
 
542
513
  for renderer in plot.state.renderers:
@@ -847,38 +818,20 @@ def plot_2d(
847
818
 
848
819
  if feature_points_4 is not None:
849
820
  updated_points_4 = feature_points_4.opts(size=size_val)
850
- feature_overlay = (
851
- updated_points_4
852
- if feature_overlay is None
853
- else feature_overlay * updated_points_4
854
- )
821
+ feature_overlay = updated_points_4 if feature_overlay is None else feature_overlay * updated_points_4
855
822
  if feature_points_3 is not None:
856
823
  updated_points_3 = feature_points_3.opts(size=size_val)
857
- feature_overlay = (
858
- updated_points_3
859
- if feature_overlay is None
860
- else feature_overlay * updated_points_3
861
- )
824
+ feature_overlay = updated_points_3 if feature_overlay is None else feature_overlay * updated_points_3
862
825
  if feature_points_1 is not None:
863
826
  updated_points_1 = feature_points_1.opts(size=size_val)
864
- feature_overlay = (
865
- updated_points_1
866
- if feature_overlay is None
867
- else feature_overlay * updated_points_1
868
- )
827
+ feature_overlay = updated_points_1 if feature_overlay is None else feature_overlay * updated_points_1
869
828
  if not show_only_features_with_ms2 and feature_points_2 is not None:
870
829
  updated_points_2 = feature_points_2.opts(size=size_val)
871
- feature_overlay = (
872
- updated_points_2
873
- if feature_overlay is None
874
- else feature_overlay * updated_points_2
875
- )
830
+ feature_overlay = updated_points_2 if feature_overlay is None else feature_overlay * updated_points_2
876
831
  if feature_points_iso is not None:
877
832
  updated_points_iso = feature_points_iso.opts(size=size_val)
878
833
  feature_overlay = (
879
- updated_points_iso
880
- if feature_overlay is None
881
- else feature_overlay * updated_points_iso
834
+ updated_points_iso if feature_overlay is None else feature_overlay * updated_points_iso
882
835
  )
883
836
 
884
837
  # Combine with the static raster background
@@ -908,12 +861,7 @@ def plot_2d(
908
861
 
909
862
  # Create the slider widget row with clear styling
910
863
  slider_widget = on.Row(
911
- on.pane.HTML(
912
- "<b>Marker Size Control:</b>",
913
- width=150,
914
- height=40,
915
- margin=(5, 10),
916
- ),
864
+ on.pane.HTML("<b>Marker Size Control:</b>", width=150, height=40, margin=(5, 10)),
917
865
  size_slider,
918
866
  height=60,
919
867
  margin=10,
@@ -979,7 +927,7 @@ def plot_2d(
979
927
  layout = panel.Column(overlay)
980
928
 
981
929
  if filename is not None:
982
- # Use consistent save/display behavior
930
+ # Use consistent save/display behavior
983
931
  self._handle_sample_plot_output(layout, filename, "panel")
984
932
  return None
985
933
  else:
@@ -1060,13 +1008,9 @@ def plot_2d_oracle(
1060
1008
  # keep only rt, mz, and inty
1061
1009
  spectradf = spectradf[["rt", "mz", "inty"]]
1062
1010
  if mz_range is not None:
1063
- spectradf = spectradf[
1064
- (spectradf["mz"] >= mz_range[0]) & (spectradf["mz"] <= mz_range[1])
1065
- ]
1011
+ spectradf = spectradf[(spectradf["mz"] >= mz_range[0]) & (spectradf["mz"] <= mz_range[1])]
1066
1012
  if rt_range is not None:
1067
- spectradf = spectradf[
1068
- (spectradf["rt"] >= rt_range[0]) & (spectradf["rt"] <= rt_range[1])
1069
- ]
1013
+ spectradf = spectradf[(spectradf["rt"] >= rt_range[0]) & (spectradf["rt"] <= rt_range[1])]
1070
1014
 
1071
1015
  maxrt = spectradf["rt"].max()
1072
1016
  minrt = spectradf["rt"].min()
@@ -1189,8 +1133,7 @@ def plot_2d_oracle(
1189
1133
  # iterate over the rows and find the feature_uid in feats by looking at the closest rt and mz
1190
1134
  for i, row in oracle_data.iterrows():
1191
1135
  candidates = feats[
1192
- (abs(feats["rt"] - row["rt"]) < 1)
1193
- & (abs(feats["mz"] - row["precursor"]) < 0.005)
1136
+ (abs(feats["rt"] - row["rt"]) < 1) & (abs(feats["mz"] - row["precursor"]) < 0.005)
1194
1137
  ].copy()
1195
1138
  if len(candidates) > 0:
1196
1139
  # sort by delta rt
@@ -1236,9 +1179,7 @@ def plot_2d_oracle(
1236
1179
  cmap_provider = "colorcet"
1237
1180
  cm = process_cmap(cmap, ncolors=num_colors, provider=cmap_provider)
1238
1181
  colors = [
1239
- rgb2hex(cm[int(i * (len(cm) - 1) / (num_colors - 1))])
1240
- if num_colors > 1
1241
- else rgb2hex(cm[0])
1182
+ rgb2hex(cm[int(i * (len(cm) - 1) / (num_colors - 1))]) if num_colors > 1 else rgb2hex(cm[0])
1242
1183
  for i in range(num_colors)
1243
1184
  ]
1244
1185
  # assign color to each row based on id_class. If id_class is null, assign 'black'
@@ -1868,9 +1809,7 @@ def plot_ms2_q1(
1868
1809
  scans = self.scans_df.filter(pl.col("cycle") == cycle)
1869
1810
  scans = scans.filter(pl.col("ms_level") == 2)
1870
1811
  # find the scan in cycle whose 'prec_mz' is the closest to the feature['mz']
1871
- scan_uid = scans[(scans["prec_mz"] - feature["mz"]).abs().arg_sort()[:1]][
1872
- "scan_uid"
1873
- ][0]
1812
+ scan_uid = scans[(scans["prec_mz"] - feature["mz"]).abs().arg_sort()[:1]]["scan_uid"][0]
1874
1813
  # get q1_width scans before and after the scan_uid
1875
1814
  scans = self.scans_df.filter(pl.col("scan_uid") >= scan_uid - q1_width)
1876
1815
  scans = scans.filter(pl.col("scan_uid") <= scan_uid + q1_width)
@@ -2134,7 +2073,7 @@ def plot_tic(
2134
2073
  return
2135
2074
 
2136
2075
  # Import helper locally to avoid circular imports
2137
- from master.study.helpers import get_tic
2076
+ from masster.study.helpers import get_tic
2138
2077
 
2139
2078
  # Delegate TIC computation to study helper which handles ms1_df and scans_df fallbacks
2140
2079
  try:
@@ -2189,7 +2128,7 @@ def plot_bpc(
2189
2128
  return
2190
2129
 
2191
2130
  # Import helper locally to avoid circular imports
2192
- from master.study.helpers import get_bpc
2131
+ from masster.study.helpers import get_bpc
2193
2132
 
2194
2133
  # Delegate BPC computation to study helper
2195
2134
  try: