masster 0.5.8__py3-none-any.whl → 0.5.10__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 +58 -43
- masster/sample/adducts.py +2 -2
- masster/sample/h5.py +1 -1
- masster/sample/helpers.py +47 -15
- masster/sample/plot.py +706 -578
- masster/sample/processing.py +4 -4
- masster/sample/sample.py +91 -48
- masster/sample/save.py +5 -5
- masster/study/h5.py +32 -14
- masster/study/helpers.py +27 -8
- masster/study/id.py +3 -3
- masster/study/load.py +1 -164
- masster/study/merge.py +6 -12
- masster/study/plot.py +105 -35
- masster/study/processing.py +7 -7
- masster/study/study5_schema.json +3 -0
- {masster-0.5.8.dist-info → masster-0.5.10.dist-info}/METADATA +3 -1
- {masster-0.5.8.dist-info → masster-0.5.10.dist-info}/RECORD +22 -22
- {masster-0.5.8.dist-info → masster-0.5.10.dist-info}/WHEEL +0 -0
- {masster-0.5.8.dist-info → masster-0.5.10.dist-info}/entry_points.txt +0 -0
- {masster-0.5.8.dist-info → masster-0.5.10.dist-info}/licenses/LICENSE +0 -0
masster/_version.py
CHANGED
masster/logger.py
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
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
|
-
#
|
|
6
|
+
# Universal colors compatible with both dark and light themes
|
|
7
7
|
level_colors = {
|
|
8
|
-
'TRACE': '\x1b[
|
|
9
|
-
'DEBUG': '\x1b[
|
|
10
|
-
'INFO': '\x1b[
|
|
11
|
-
'SUCCESS': '\x1b[
|
|
12
|
-
'WARNING': '\x1b[
|
|
13
|
-
'ERROR': '\x1b[
|
|
14
|
-
'CRITICAL': '\x1b[
|
|
8
|
+
'TRACE': '\x1b[94m', # bright blue (readable on both dark/light)
|
|
9
|
+
'DEBUG': '\x1b[96m', # bright cyan (readable on both dark/light)
|
|
10
|
+
'INFO': '\x1b[90m', # bright black/gray (readable on both dark/light)
|
|
11
|
+
'SUCCESS': '\x1b[92m', # bright green (readable on both dark/light)
|
|
12
|
+
'WARNING': '\x1b[93m', # bright yellow (readable on both dark/light)
|
|
13
|
+
'ERROR': '\x1b[91m', # bright red (readable on both dark/light)
|
|
14
|
+
'CRITICAL': '\x1b[95m', # bright magenta (readable on both dark/light)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
level_str = record.levelname.ljust(8)complex loguru filtering.
|
|
@@ -102,19 +102,19 @@ class MassterLogger:
|
|
|
102
102
|
dt = datetime.datetime.fromtimestamp(record.created)
|
|
103
103
|
timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # Remove last 3 digits for milliseconds
|
|
104
104
|
|
|
105
|
-
#
|
|
105
|
+
# Universal colors compatible with both dark and light themes
|
|
106
106
|
level_colors = {
|
|
107
|
-
"TRACE": "\x1b[
|
|
108
|
-
"DEBUG": "\x1b[
|
|
109
|
-
"INFO": "\x1b[
|
|
110
|
-
"SUCCESS": "\x1b[
|
|
111
|
-
"WARNING": "\x1b[
|
|
112
|
-
"ERROR": "\x1b[
|
|
113
|
-
"CRITICAL": "\x1b[
|
|
107
|
+
"TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
|
|
108
|
+
"DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
|
|
109
|
+
"INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
|
|
110
|
+
"SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
|
|
111
|
+
"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
114
|
}
|
|
115
115
|
|
|
116
116
|
level_str = record.levelname.ljust(8)
|
|
117
|
-
level_color = level_colors.get(record.levelname, "\x1b[
|
|
117
|
+
level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
|
|
118
118
|
label_part = self.label + " | " if self.label else ""
|
|
119
119
|
|
|
120
120
|
# For DEBUG and TRACE levels, add module/location information
|
|
@@ -133,9 +133,9 @@ class MassterLogger:
|
|
|
133
133
|
f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
-
#
|
|
136
|
+
# Universal format: timestamp | level | location | label - message
|
|
137
137
|
return (
|
|
138
|
-
f"\x1b[
|
|
138
|
+
f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
|
|
139
139
|
f"{level_color}{level_str}\x1b[0m | " # colored level
|
|
140
140
|
f"{location_info}" # location info for DEBUG/TRACE
|
|
141
141
|
f"{level_color}{label_part}\x1b[0m" # colored label
|
|
@@ -181,19 +181,19 @@ class MassterLogger:
|
|
|
181
181
|
dt = datetime.datetime.fromtimestamp(record.created)
|
|
182
182
|
timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
183
183
|
|
|
184
|
-
#
|
|
184
|
+
# Universal colors compatible with both dark and light themes
|
|
185
185
|
level_colors = {
|
|
186
|
-
"TRACE": "\x1b[
|
|
187
|
-
"DEBUG": "\x1b[
|
|
188
|
-
"INFO": "\x1b[
|
|
189
|
-
"SUCCESS": "\x1b[
|
|
190
|
-
"WARNING": "\x1b[
|
|
191
|
-
"ERROR": "\x1b[
|
|
192
|
-
"CRITICAL": "\x1b[
|
|
186
|
+
"TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
|
|
187
|
+
"DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
|
|
188
|
+
"INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
|
|
189
|
+
"SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
|
|
190
|
+
"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)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
level_str = record.levelname.ljust(8)
|
|
196
|
-
level_color = level_colors.get(record.levelname, "\x1b[
|
|
196
|
+
level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
|
|
197
197
|
label_part = self.label + " | " if self.label else ""
|
|
198
198
|
|
|
199
199
|
# For DEBUG and TRACE levels, add module/location information
|
|
@@ -212,9 +212,9 @@ class MassterLogger:
|
|
|
212
212
|
f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
-
#
|
|
215
|
+
# Universal format: timestamp | level | location | label - message
|
|
216
216
|
return (
|
|
217
|
-
f"\x1b[
|
|
217
|
+
f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
|
|
218
218
|
f"{level_color}{level_str}\x1b[0m | " # colored level
|
|
219
219
|
f"{location_info}" # location info for DEBUG/TRACE
|
|
220
220
|
f"{level_color}{label_part}\x1b[0m" # colored label
|
|
@@ -245,19 +245,19 @@ class MassterLogger:
|
|
|
245
245
|
dt = datetime.datetime.fromtimestamp(record.created)
|
|
246
246
|
timestamp = dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
247
247
|
|
|
248
|
-
#
|
|
248
|
+
# Universal colors compatible with both dark and light themes
|
|
249
249
|
level_colors = {
|
|
250
|
-
"TRACE": "\x1b[
|
|
251
|
-
"DEBUG": "\x1b[
|
|
252
|
-
"INFO": "\x1b[
|
|
253
|
-
"SUCCESS": "\x1b[
|
|
254
|
-
"WARNING": "\x1b[
|
|
255
|
-
"ERROR": "\x1b[
|
|
256
|
-
"CRITICAL": "\x1b[
|
|
250
|
+
"TRACE": "\x1b[94m", # bright blue (readable on both dark/light)
|
|
251
|
+
"DEBUG": "\x1b[96m", # bright cyan (readable on both dark/light)
|
|
252
|
+
"INFO": "\x1b[90m", # bright black/gray (readable on both dark/light)
|
|
253
|
+
"SUCCESS": "\x1b[92m", # bright green (readable on both dark/light)
|
|
254
|
+
"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)
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
level_str = record.levelname.ljust(8)
|
|
260
|
-
level_color = level_colors.get(record.levelname, "\x1b[
|
|
260
|
+
level_color = level_colors.get(record.levelname, "\x1b[90m") # default to gray instead of white
|
|
261
261
|
label_part = self.label + " | " if self.label else ""
|
|
262
262
|
|
|
263
263
|
# For DEBUG and TRACE levels, add module/location information
|
|
@@ -276,9 +276,9 @@ class MassterLogger:
|
|
|
276
276
|
f"\x1b[90m{module_name}:{func_name}:{line_no}\x1b[0m | " # dim gray for location info
|
|
277
277
|
)
|
|
278
278
|
|
|
279
|
-
#
|
|
279
|
+
# Universal format: timestamp | level | location | label - message
|
|
280
280
|
return (
|
|
281
|
-
f"\x1b[
|
|
281
|
+
f"\x1b[90m{timestamp}\x1b[0m | " # gray timestamp (universal for both themes)
|
|
282
282
|
f"{level_color}{level_str}\x1b[0m | " # colored level
|
|
283
283
|
f"{location_info}" # location info for DEBUG/TRACE
|
|
284
284
|
f"{level_color}{label_part}\x1b[0m" # colored label
|
|
@@ -332,8 +332,22 @@ class MassterLogger:
|
|
|
332
332
|
self.logger_instance.info(message, *args, **kwargs)
|
|
333
333
|
|
|
334
334
|
def success(self, message: str, *args, **kwargs):
|
|
335
|
-
"""Log a SUCCESS level message (
|
|
336
|
-
|
|
335
|
+
"""Log a SUCCESS level message (custom level)."""
|
|
336
|
+
# Create a custom log record with SUCCESS level
|
|
337
|
+
import logging
|
|
338
|
+
|
|
339
|
+
# Create a LogRecord manually with SUCCESS level
|
|
340
|
+
record = self.logger_instance.makeRecord(
|
|
341
|
+
self.logger_instance.name,
|
|
342
|
+
logging.INFO, # Use INFO level for Python's filtering
|
|
343
|
+
"", 0, message, args, None, func="success"
|
|
344
|
+
)
|
|
345
|
+
# Override the levelname for display
|
|
346
|
+
record.levelname = "SUCCESS"
|
|
347
|
+
|
|
348
|
+
# Handle the record directly through our handler
|
|
349
|
+
if self.handler:
|
|
350
|
+
self.handler.handle(record)
|
|
337
351
|
|
|
338
352
|
def warning(self, message: str, *args, **kwargs):
|
|
339
353
|
"""Log a WARNING level message."""
|
|
@@ -372,3 +386,4 @@ class MassterLogger:
|
|
|
372
386
|
|
|
373
387
|
def __repr__(self):
|
|
374
388
|
return f"MassterLogger(type={self.instance_type}, id={self.instance_id}, level={self.level})"
|
|
389
|
+
|
masster/sample/adducts.py
CHANGED
|
@@ -798,8 +798,8 @@ def find_adducts(self, **kwargs):
|
|
|
798
798
|
total_with_adducts = sum(1 for x in final_adducts if x is not None)
|
|
799
799
|
total_groups = max(final_groups) if final_groups else 0
|
|
800
800
|
|
|
801
|
-
self.logger.
|
|
802
|
-
f"Adduct detection completed: {total_with_adducts}
|
|
801
|
+
self.logger.success(
|
|
802
|
+
f"Adduct detection completed. Features with adducts: {total_with_adducts}. Adduct groups: {total_groups}.",
|
|
803
803
|
)
|
|
804
804
|
|
|
805
805
|
# Store parameters including the actual processed adducts list
|
masster/sample/h5.py
CHANGED
|
@@ -295,7 +295,7 @@ def _save_sample5(
|
|
|
295
295
|
|
|
296
296
|
# Store lib and lib_match - removed (no longer saving lib data)
|
|
297
297
|
|
|
298
|
-
self.logger.
|
|
298
|
+
self.logger.success(f"Sample saved to {filename}")
|
|
299
299
|
if save_featurexml:
|
|
300
300
|
# Get or recreate the feature map if needed
|
|
301
301
|
feature_map = self._get_feature_map()
|
masster/sample/helpers.py
CHANGED
|
@@ -360,6 +360,7 @@ def get_eic(self, mz, mz_tol=None):
|
|
|
360
360
|
|
|
361
361
|
def features_select(
|
|
362
362
|
self,
|
|
363
|
+
uid=None,
|
|
363
364
|
mz=None,
|
|
364
365
|
rt=None,
|
|
365
366
|
coherence=None,
|
|
@@ -369,15 +370,16 @@ def features_select(
|
|
|
369
370
|
iso_of=None,
|
|
370
371
|
has_MS2=None,
|
|
371
372
|
prominence_scaled=None,
|
|
372
|
-
height_scaled=None,
|
|
373
373
|
prominence=None,
|
|
374
|
+
height_scaled=None,
|
|
374
375
|
height=None,
|
|
375
|
-
|
|
376
|
+
adduct_group=None,
|
|
376
377
|
):
|
|
377
378
|
"""
|
|
378
379
|
Select features based on specified criteria and return the filtered DataFrame.
|
|
379
380
|
|
|
380
381
|
Parameters:
|
|
382
|
+
uid: feature UID filter (list of feature UIDs, tuple for range of feature UIDs, polars/pandas DataFrame with feature_uid/feature_id column, or None for all features)
|
|
381
383
|
mz: m/z range filter (tuple for range, single value for minimum)
|
|
382
384
|
rt: retention time range filter (tuple for range, single value for minimum)
|
|
383
385
|
coherence: chromatogram coherence filter (tuple for range, single value for minimum)
|
|
@@ -390,8 +392,7 @@ def features_select(
|
|
|
390
392
|
height_scaled: scaled height filter (tuple for range, single value for minimum)
|
|
391
393
|
prominence: prominence filter (tuple for range, single value for minimum)
|
|
392
394
|
height: height filter (tuple for range, single value for minimum)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
adduct_group: adduct group filter (single value for exact match, list of values for multiple groups, tuple for range, or None for all)
|
|
395
396
|
Returns:
|
|
396
397
|
polars.DataFrame: Filtered features DataFrame
|
|
397
398
|
"""
|
|
@@ -402,17 +403,29 @@ def features_select(
|
|
|
402
403
|
feats = self.features_df.clone()
|
|
403
404
|
|
|
404
405
|
# Filter by feature UIDs if provided
|
|
405
|
-
if
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
406
|
+
if uid is not None:
|
|
407
|
+
if isinstance(uid, tuple) and len(uid) == 2:
|
|
408
|
+
# Handle tuple as range of feature UIDs
|
|
409
|
+
min_uid, max_uid = uid
|
|
410
|
+
feats_len_before_filter = len(feats)
|
|
411
|
+
feats = feats.filter(
|
|
412
|
+
(pl.col("feature_uid") >= min_uid) & (pl.col("feature_uid") <= max_uid)
|
|
413
|
+
)
|
|
414
|
+
self.logger.debug(
|
|
415
|
+
f"Selected features by UID range ({min_uid}-{max_uid}). Features removed: {feats_len_before_filter - len(feats)}",
|
|
416
|
+
)
|
|
417
|
+
else:
|
|
418
|
+
# Handle list or DataFrame input
|
|
419
|
+
feature_uids_to_keep = self._get_feature_uids(features=uid, verbose=True)
|
|
420
|
+
if not feature_uids_to_keep:
|
|
421
|
+
self.logger.warning("No valid feature UIDs provided.")
|
|
422
|
+
return feats.limit(0) # Return empty DataFrame with same structure
|
|
423
|
+
|
|
424
|
+
feats_len_before_filter = len(feats)
|
|
425
|
+
feats = feats.filter(pl.col("feature_uid").is_in(feature_uids_to_keep))
|
|
426
|
+
self.logger.debug(
|
|
427
|
+
f"Selected features by UIDs. Features removed: {feats_len_before_filter - len(feats)}",
|
|
428
|
+
)
|
|
416
429
|
|
|
417
430
|
if coherence is not None:
|
|
418
431
|
has_coherence = "chrom_coherence" in self.features_df.columns
|
|
@@ -579,6 +592,25 @@ def features_select(
|
|
|
579
592
|
self.logger.debug(
|
|
580
593
|
f"Selected features by {height_col}. Features removed: {feats_len_before_filter - len(feats)}",
|
|
581
594
|
)
|
|
595
|
+
|
|
596
|
+
if adduct_group is not None:
|
|
597
|
+
feats_len_before_filter = len(feats)
|
|
598
|
+
if "adduct_group" not in feats.columns:
|
|
599
|
+
self.logger.warning("No adduct_group data found in features.")
|
|
600
|
+
else:
|
|
601
|
+
if isinstance(adduct_group, tuple) and len(adduct_group) == 2:
|
|
602
|
+
min_adduct_group, max_adduct_group = adduct_group
|
|
603
|
+
feats = feats.filter(
|
|
604
|
+
(pl.col("adduct_group") >= min_adduct_group) & (pl.col("adduct_group") <= max_adduct_group)
|
|
605
|
+
)
|
|
606
|
+
elif isinstance(adduct_group, list):
|
|
607
|
+
feats = feats.filter(pl.col("adduct_group").is_in(adduct_group))
|
|
608
|
+
else:
|
|
609
|
+
feats = feats.filter(pl.col("adduct_group") == adduct_group)
|
|
610
|
+
self.logger.debug(
|
|
611
|
+
f"Selected features by adduct_group. Features removed: {feats_len_before_filter - len(feats)}",
|
|
612
|
+
)
|
|
613
|
+
|
|
582
614
|
if len(feats) == 0:
|
|
583
615
|
self.logger.warning("No features remaining after applying selection criteria.")
|
|
584
616
|
else:
|