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 +1 -1
- masster/logger.py +35 -19
- masster/sample/adducts.py +15 -29
- masster/sample/defaults/find_adducts_def.py +1 -3
- masster/sample/defaults/sample_def.py +4 -4
- masster/sample/h5.py +203 -361
- masster/sample/helpers.py +14 -30
- masster/sample/lib.py +3 -3
- masster/sample/load.py +21 -29
- masster/sample/plot.py +222 -132
- masster/sample/processing.py +42 -55
- masster/sample/sample.py +37 -46
- masster/sample/save.py +37 -61
- masster/sample/sciex.py +13 -11
- masster/sample/thermo.py +69 -74
- masster/spectrum.py +15 -15
- masster/study/analysis.py +650 -586
- masster/study/defaults/identify_def.py +1 -3
- masster/study/defaults/merge_def.py +6 -7
- masster/study/defaults/study_def.py +1 -5
- masster/study/export.py +35 -96
- masster/study/h5.py +134 -211
- masster/study/helpers.py +385 -459
- masster/study/id.py +239 -290
- masster/study/importers.py +84 -93
- masster/study/load.py +159 -178
- masster/study/merge.py +1112 -1098
- masster/study/plot.py +195 -149
- masster/study/processing.py +144 -191
- masster/study/save.py +14 -13
- masster/study/study.py +89 -130
- masster/wizard/wizard.py +764 -714
- {masster-0.5.22.dist-info → masster-0.5.24.dist-info}/METADATA +27 -1
- {masster-0.5.22.dist-info → masster-0.5.24.dist-info}/RECORD +37 -37
- {masster-0.5.22.dist-info → masster-0.5.24.dist-info}/WHEEL +0 -0
- {masster-0.5.22.dist-info → masster-0.5.24.dist-info}/entry_points.txt +0 -0
- {masster-0.5.22.dist-info → masster-0.5.24.dist-info}/licenses/LICENSE +0 -0
masster/_version.py
CHANGED
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",
|
|
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",
|
|
113
|
-
"CRITICAL": "\x1b[95m",
|
|
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,
|
|
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",
|
|
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",
|
|
192
|
-
"CRITICAL": "\x1b[95m",
|
|
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",
|
|
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",
|
|
256
|
-
"CRITICAL": "\x1b[95m",
|
|
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
|
-
"",
|
|
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,
|
|
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,
|
|
437
|
-
if self.polarity.lower() in [
|
|
438
|
-
params.set(
|
|
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 [
|
|
441
|
-
params.set(
|
|
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
|
|