masster 0.5.22__py3-none-any.whl → 0.5.24__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/_version.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
 
4
- __version__ = "0.5.22"
4
+ __version__ = "0.5.24"
5
5
 
6
6
 
7
7
  def get_version():
masster/logger.py CHANGED
@@ -3,10 +3,11 @@
3
3
  Simple logger system for masster Study and Sample instances.
4
4
  Uses basic Python logging timestamp = dt.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
5
5
 
6
+ # Universal colors compatible with both dark and light themes
6
7
  # Universal colors compatible with both dark and light themes
7
8
  level_colors = {
8
9
  'TRACE': '\x1b[94m', # bright blue (readable on both dark/light)
9
- 'DEBUG': '\x1b[96m', # bright cyan (readable on both dark/light)
10
+ 'DEBUG': '\x1b[96m', # bright cyan (readable on both dark/light)
10
11
  'INFO': '\x1b[90m', # bright black/gray (readable on both dark/light)
11
12
  'SUCCESS': '\x1b[92m', # bright green (readable on both dark/light)
12
13
  'WARNING': '\x1b[93m', # bright yellow (readable on both dark/light)
@@ -70,7 +71,7 @@ class MassterLogger:
70
71
  # Remove any existing handlers to prevent duplicates
71
72
  if self.logger_instance.hasHandlers():
72
73
  self.logger_instance.handlers.clear()
73
-
74
+
74
75
  # Also ensure no duplicate handlers on parent loggers
75
76
  parent = self.logger_instance.parent
76
77
  while parent:
@@ -102,19 +103,21 @@ class MassterLogger:
102
103
  dt = datetime.datetime.fromtimestamp(record.created)
103
104
  timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # Remove last 3 digits for milliseconds
104
105
 
106
+ # Universal colors compatible with both dark and light themes
105
107
  # Universal colors compatible with both dark and light themes
106
108
  level_colors = {
107
109
  "TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
108
110
  "DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
109
- "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
111
+ "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
110
112
  "SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
111
113
  "WARNING": "\x1b[93m", # bright yellow (readable on both dark/light)
112
- "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
113
- "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
114
+ "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
115
+ "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
114
116
  }
115
117
 
116
118
  level_str = record.levelname.ljust(8)
117
119
  level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
120
+ level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
118
121
  label_part = self.label + " | " if self.label else ""
119
122
 
120
123
  # For DEBUG and TRACE levels, add module/location information
@@ -133,8 +136,10 @@ class MassterLogger:
133
136
  f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
134
137
  )
135
138
 
139
+ # Universal format: timestamp | level | location | label - message
136
140
  # Universal format: timestamp | level | location | label - message
137
141
  return (
142
+ f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
138
143
  f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
139
144
  f"{level_color}{level_str}\x1b[0m | " # colored level
140
145
  f"{location_info}" # location info for DEBUG/TRACE
@@ -147,14 +152,14 @@ class MassterLogger:
147
152
 
148
153
  # Prevent propagation to avoid duplicate messages
149
154
  self.logger_instance.propagate = False
150
-
155
+
151
156
  # Additional fix: ensure no duplicate handlers in the entire logging hierarchy
152
157
  masster_logger = logging.getLogger("masster")
153
158
  if masster_logger.hasHandlers():
154
159
  # Keep only one handler per type
155
160
  unique_handlers = {}
156
161
  for handler in masster_logger.handlers:
157
- handler_key = (type(handler).__name__, getattr(handler, 'stream', None))
162
+ handler_key = (type(handler).__name__, getattr(handler, "stream", None))
158
163
  if handler_key not in unique_handlers:
159
164
  unique_handlers[handler_key] = handler
160
165
  masster_logger.handlers = list(unique_handlers.values())
@@ -181,19 +186,21 @@ class MassterLogger:
181
186
  dt = datetime.datetime.fromtimestamp(record.created)
182
187
  timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
183
188
 
189
+ # Universal colors compatible with both dark and light themes
184
190
  # Universal colors compatible with both dark and light themes
185
191
  level_colors = {
186
192
  "TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
187
193
  "DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
188
- "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
194
+ "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
189
195
  "SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
190
196
  "WARNING": "\x1b[93m", # bright yellow (readable on both dark/light)
191
- "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
192
- "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
197
+ "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
198
+ "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
193
199
  }
194
200
 
195
201
  level_str = record.levelname.ljust(8)
196
202
  level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
203
+ level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
197
204
  label_part = self.label + " | " if self.label else ""
198
205
 
199
206
  # For DEBUG and TRACE levels, add module/location information
@@ -212,8 +219,9 @@ class MassterLogger:
212
219
  f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
213
220
  )
214
221
 
215
- # Universal format: timestamp | level | location | label - message
222
+ # Universal format: timestamp | level | location | label - message
216
223
  return (
224
+ f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
217
225
  f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
218
226
  f"{level_color}{level_str}\x1b[0m | " # colored level
219
227
  f"{location_info}" # location info for DEBUG/TRACE
@@ -245,19 +253,21 @@ class MassterLogger:
245
253
  dt = datetime.datetime.fromtimestamp(record.created)
246
254
  timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
247
255
 
256
+ # Universal colors compatible with both dark and light themes
248
257
  # Universal colors compatible with both dark and light themes
249
258
  level_colors = {
250
259
  "TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
251
260
  "DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
252
- "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
261
+ "INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
253
262
  "SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
254
263
  "WARNING": "\x1b[93m", # bright yellow (readable on both dark/light)
255
- "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
256
- "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
264
+ "ERROR": "\x1b[91m", # bright red (readable on both dark/light)
265
+ "CRITICAL": "\x1b[95m", # bright magenta (readable on both dark/light)
257
266
  }
258
267
 
259
268
  level_str = record.levelname.ljust(8)
260
269
  level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
270
+ level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
261
271
  label_part = self.label + " | " if self.label else ""
262
272
 
263
273
  # For DEBUG and TRACE levels, add module/location information
@@ -276,8 +286,9 @@ class MassterLogger:
276
286
  f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
277
287
  )
278
288
 
279
- # Universal format: timestamp | level | location | label - message
289
+ # Universal format: timestamp | level | location | label - message
280
290
  return (
291
+ f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
281
292
  f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
282
293
  f"{level_color}{level_str}\x1b[0m | " # colored level
283
294
  f"{location_info}" # location info for DEBUG/TRACE
@@ -335,16 +346,21 @@ class MassterLogger:
335
346
  """Log a SUCCESS level message (custom level)."""
336
347
  # Create a custom log record with SUCCESS level
337
348
  import logging
338
-
349
+
339
350
  # Create a LogRecord manually with SUCCESS level
340
351
  record = self.logger_instance.makeRecord(
341
352
  self.logger_instance.name,
342
353
  logging.INFO, # Use INFO level for Python's filtering
343
- "", 0, message, args, None, func="success"
354
+ "",
355
+ 0,
356
+ message,
357
+ args,
358
+ None,
359
+ func="success",
344
360
  )
345
361
  # Override the levelname for display
346
362
  record.levelname = "SUCCESS"
347
-
363
+
348
364
  # Handle the record directly through our handler
349
365
  if self.handler:
350
366
  self.handler.handle(record)
@@ -370,7 +386,7 @@ class MassterLogger:
370
386
  if self.handler:
371
387
  self.logger_instance.removeHandler(self.handler)
372
388
  # Close the file handle if it's not stdout
373
- if hasattr(self.sink, 'close') and self.sink != sys.stdout:
389
+ if hasattr(self.sink, "close") and self.sink != sys.stdout:
374
390
  try:
375
391
  self.sink.close()
376
392
  except Exception:
masster/sample/adducts.py CHANGED
@@ -156,11 +156,9 @@ def _get_adducts(self, adducts_list: list = None, **kwargs):
156
156
  {
157
157
  "components": components,
158
158
  "formatted_name": formatted_name,
159
- "total_mass_shift": pos_spec["mass_shift"]
160
- + neut_spec["mass_shift"],
159
+ "total_mass_shift": pos_spec["mass_shift"] + neut_spec["mass_shift"],
161
160
  "total_charge": total_charge,
162
- "combined_probability": pos_spec["probability"]
163
- * neut_spec["probability"],
161
+ "combined_probability": pos_spec["probability"] * neut_spec["probability"],
164
162
  "complexity": 2,
165
163
  },
166
164
  )
@@ -176,11 +174,9 @@ def _get_adducts(self, adducts_list: list = None, **kwargs):
176
174
  {
177
175
  "components": components,
178
176
  "formatted_name": formatted_name,
179
- "total_mass_shift": combo[0]["mass_shift"]
180
- + combo[1]["mass_shift"],
177
+ "total_mass_shift": combo[0]["mass_shift"] + combo[1]["mass_shift"],
181
178
  "total_charge": total_charge,
182
- "combined_probability": combo[0]["probability"]
183
- * combo[1]["probability"],
179
+ "combined_probability": combo[0]["probability"] * combo[1]["probability"],
184
180
  "complexity": 2,
185
181
  },
186
182
  )
@@ -380,9 +376,7 @@ def _format_adduct_name(components: List[Dict]) -> str:
380
376
  elif abs(total_charge) == 1:
381
377
  charge_str = "1+" if total_charge > 0 else "1-"
382
378
  else:
383
- charge_str = (
384
- f"{abs(total_charge)}+" if total_charge > 0 else f"{abs(total_charge)}-"
385
- )
379
+ charge_str = f"{abs(total_charge)}+" if total_charge > 0 else f"{abs(total_charge)}-"
386
380
 
387
381
  return f"[M{formula}]{charge_str}"
388
382
 
@@ -433,12 +427,12 @@ def find_adducts(self, **kwargs):
433
427
  self.logger.warning(f"Unknown parameter {key} ignored")
434
428
 
435
429
  # Auto-set adducts based on sample polarity if not explicitly provided
436
- if params.adducts is None and hasattr(self, 'polarity') and self.polarity is not None:
437
- if self.polarity.lower() in ['positive', 'pos']:
438
- params.set('adducts', 'positive', validate=True)
430
+ if params.adducts is None and hasattr(self, "polarity") and self.polarity is not None:
431
+ if self.polarity.lower() in ["positive", "pos"]:
432
+ params.set("adducts", "positive", validate=True)
439
433
  self.logger.debug(f"Auto-set adducts to 'positive' based on sample polarity: {self.polarity}")
440
- elif self.polarity.lower() in ['negative', 'neg']:
441
- params.set('adducts', 'negative', validate=True)
434
+ elif self.polarity.lower() in ["negative", "neg"]:
435
+ params.set("adducts", "negative", validate=True)
442
436
  self.logger.debug(f"Auto-set adducts to 'negative' based on sample polarity: {self.polarity}")
443
437
  else:
444
438
  self.logger.debug(f"Unknown sample polarity '{self.polarity}', using default adducts")
@@ -488,7 +482,7 @@ def find_adducts(self, **kwargs):
488
482
  self.logger.debug(f"Min probability threshold: {min_probability}")
489
483
 
490
484
  # Generate comprehensive adduct specifications using the Sample method
491
- adducts_df = self._get_adducts(
485
+ adducts_df = self._get_adducts(
492
486
  adducts_list=adducts_list,
493
487
  charge_min=charge_min,
494
488
  charge_max=charge_max,
@@ -572,9 +566,7 @@ def find_adducts(self, **kwargs):
572
566
  tol2 = mass_max_diff * mz2 * 1e-6
573
567
  combined_tolerance = tol1 + tol2
574
568
  else: # Da
575
- combined_tolerance = (
576
- 2 * mass_max_diff
577
- ) # Combined tolerance for both features
569
+ combined_tolerance = 2 * mass_max_diff # Combined tolerance for both features
578
570
 
579
571
  # Check both directions of mass relationship
580
572
  if charge != 0:
@@ -678,9 +670,7 @@ def find_adducts(self, **kwargs):
678
670
 
679
671
  # Calculate neutral mass for base ion
680
672
  base_mz_measured = feature_mzs[base_feature_idx]
681
- neutral_mass_assignments[base_feature_idx] = (
682
- base_mz_measured * abs(base_charge) - base_mass_shift
683
- )
673
+ neutral_mass_assignments[base_feature_idx] = base_mz_measured * abs(base_charge) - base_mass_shift
684
674
 
685
675
  # Assign other features based on their relationships to base
686
676
  for feature_idx in component:
@@ -726,9 +716,7 @@ def find_adducts(self, **kwargs):
726
716
  mass_shift_assignments[feature_idx] = best_mass_shift
727
717
 
728
718
  # Calculate neutral mass
729
- neutral_mass_assignments[feature_idx] = (
730
- feature_mz * abs(best_charge) - best_mass_shift
731
- )
719
+ neutral_mass_assignments[feature_idx] = feature_mz * abs(best_charge) - best_mass_shift
732
720
 
733
721
  # Assign fallback adduct for features not processed in connected components (isolated features)
734
722
  for i in range(n_features):
@@ -743,9 +731,7 @@ def find_adducts(self, **kwargs):
743
731
 
744
732
  # Calculate neutral mass for isolated features
745
733
  feature_mz = feature_mzs[i]
746
- neutral_mass_assignments[i] = (
747
- feature_mz * abs(fallback_charge) - fallback_mass_shift
748
- )
734
+ neutral_mass_assignments[i] = feature_mz * abs(fallback_charge) - fallback_mass_shift
749
735
 
750
736
  # Map back to original feature order using stored positions
751
737
  original_indices = features_sorted.select("original_position").to_numpy().flatten()
@@ -58,9 +58,7 @@ class find_adducts_defaults:
58
58
  retention_max_diff_local: float = 1.0 # 1 second - precise RT grouping
59
59
 
60
60
  # Mass tolerance constraints
61
- mass_max_diff: float = (
62
- 0.01 # 0.01 Da - strict mass tolerance for chemical specificity
63
- )
61
+ mass_max_diff: float = 0.01 # 0.01 Da - strict mass tolerance for chemical specificity
64
62
  unit: str = "Da" # Mass tolerance unit: "Da" or "ppm"
65
63
 
66
64
  # Probability filtering
@@ -46,12 +46,12 @@ class sample_defaults:
46
46
  # file and data handling settings
47
47
  type: str = "dda"
48
48
  polarity: str | None = None
49
-
49
+
50
50
  # chromatographic settings
51
- #chrom_fwhm: float = 1.0
51
+ # chrom_fwhm: float = 1.0
52
52
  eic_mz_tol: float = 0.01
53
53
  eic_rt_tol: float = 10.0
54
-
54
+
55
55
  # mz tolerances
56
56
  mz_tol_ms1_da: float = 0.002
57
57
  mz_tol_ms2_da: float = 0.005
@@ -65,7 +65,7 @@ class sample_defaults:
65
65
  centroid_smooth: int = 5
66
66
  centroid_refine: bool = True
67
67
  centroid_prominence: int = -1
68
-
68
+
69
69
  # data retrieval settings
70
70
  max_points_per_spectrum: int = 50000
71
71