masster 0.4.16__py3-none-any.whl → 0.4.17__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/study/defaults/merge_def.py +66 -3
- masster/study/merge.py +556 -18
- masster/study/plot.py +15 -1
- {masster-0.4.16.dist-info → masster-0.4.17.dist-info}/METADATA +3 -2
- {masster-0.4.16.dist-info → masster-0.4.17.dist-info}/RECORD +9 -9
- {masster-0.4.16.dist-info → masster-0.4.17.dist-info}/WHEEL +0 -0
- {masster-0.4.16.dist-info → masster-0.4.17.dist-info}/entry_points.txt +0 -0
- {masster-0.4.16.dist-info → masster-0.4.17.dist-info}/licenses/LICENSE +0 -0
masster/_version.py
CHANGED
|
@@ -25,7 +25,7 @@ class merge_defaults:
|
|
|
25
25
|
link_ms2 (bool): Whether to link MS2 spectra to consensus features. Default is True.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
method: str = "
|
|
28
|
+
method: str = "quality"
|
|
29
29
|
min_samples: int = 10
|
|
30
30
|
rt_tol: float = 5.0
|
|
31
31
|
mz_tol: float = 0.01
|
|
@@ -35,14 +35,25 @@ class merge_defaults:
|
|
|
35
35
|
max_pairwise_log_fc: float = -1.0
|
|
36
36
|
max_nr_conflicts: int = 0
|
|
37
37
|
link_ms2: bool = True
|
|
38
|
+
|
|
39
|
+
# KD-Strict specific parameters
|
|
40
|
+
optimize_rt_tol: bool = False
|
|
41
|
+
rt_tol_range: tuple = (0.8, 2.0)
|
|
42
|
+
rt_tol_steps: int = 5
|
|
43
|
+
secondary_merge_rt_tol: float = 0.5
|
|
44
|
+
secondary_merge_mz_tol: float = 0.005
|
|
45
|
+
min_sample_overlap: float = 0.8
|
|
46
|
+
max_rt_spread: float = None # Will default to 2x rt_tol
|
|
47
|
+
min_coherence: float = 0.0
|
|
38
48
|
|
|
39
49
|
_param_metadata: dict[str, dict[str, Any]] = field(
|
|
40
50
|
default_factory=lambda: {
|
|
41
51
|
"method": {
|
|
42
52
|
"dtype": str,
|
|
43
53
|
"description": "Merge method (algorithm) to use",
|
|
44
|
-
"default": "
|
|
45
|
-
"allowed_values": ["
|
|
54
|
+
"default": "quality",
|
|
55
|
+
"allowed_values": ["sensitivity", "qt", "nowarp", "chunked", "quality",
|
|
56
|
+
"kd", "kd-nowarp", "kd_nowarp", "kd-strict", "kd_strict"],
|
|
46
57
|
},
|
|
47
58
|
"min_samples": {
|
|
48
59
|
"dtype": int,
|
|
@@ -103,6 +114,58 @@ class merge_defaults:
|
|
|
103
114
|
"description": "Whether to link MS2 spectra to consensus features",
|
|
104
115
|
"default": True,
|
|
105
116
|
},
|
|
117
|
+
# KD-Strict specific parameters
|
|
118
|
+
"optimize_rt_tol": {
|
|
119
|
+
"dtype": bool,
|
|
120
|
+
"description": "Enable RT tolerance optimization for kd-strict method",
|
|
121
|
+
"default": False,
|
|
122
|
+
},
|
|
123
|
+
"rt_tol_range": {
|
|
124
|
+
"dtype": tuple,
|
|
125
|
+
"description": "RT tolerance range for optimization (min, max) in seconds",
|
|
126
|
+
"default": (0.8, 2.0),
|
|
127
|
+
},
|
|
128
|
+
"rt_tol_steps": {
|
|
129
|
+
"dtype": int,
|
|
130
|
+
"description": "Number of steps for RT tolerance optimization",
|
|
131
|
+
"default": 5,
|
|
132
|
+
"min_value": 3,
|
|
133
|
+
"max_value": 20,
|
|
134
|
+
},
|
|
135
|
+
"secondary_merge_rt_tol": {
|
|
136
|
+
"dtype": float,
|
|
137
|
+
"description": "RT tolerance for secondary clustering in kd-strict (seconds)",
|
|
138
|
+
"default": 0.5,
|
|
139
|
+
"min_value": 0.1,
|
|
140
|
+
"max_value": 5.0,
|
|
141
|
+
},
|
|
142
|
+
"secondary_merge_mz_tol": {
|
|
143
|
+
"dtype": float,
|
|
144
|
+
"description": "m/z tolerance for secondary clustering in kd-strict (Da)",
|
|
145
|
+
"default": 0.005,
|
|
146
|
+
"min_value": 0.001,
|
|
147
|
+
"max_value": 0.1,
|
|
148
|
+
},
|
|
149
|
+
"min_sample_overlap": {
|
|
150
|
+
"dtype": float,
|
|
151
|
+
"description": "Minimum sample overlap ratio for merging features (0.0-1.0)",
|
|
152
|
+
"default": 0.8,
|
|
153
|
+
"min_value": 0.0,
|
|
154
|
+
"max_value": 1.0,
|
|
155
|
+
},
|
|
156
|
+
"max_rt_spread": {
|
|
157
|
+
"dtype": float,
|
|
158
|
+
"description": "Maximum allowed RT spread in seconds (None = 3x rt_tol)",
|
|
159
|
+
"default": None,
|
|
160
|
+
"min_value": 0.1,
|
|
161
|
+
},
|
|
162
|
+
"min_coherence": {
|
|
163
|
+
"dtype": float,
|
|
164
|
+
"description": "Minimum chromatographic coherence score (0.0 = disabled)",
|
|
165
|
+
"default": 0.0,
|
|
166
|
+
"min_value": 0.0,
|
|
167
|
+
"max_value": 1.0,
|
|
168
|
+
},
|
|
106
169
|
},
|
|
107
170
|
repr=False,
|
|
108
171
|
)
|
masster/study/merge.py
CHANGED
|
@@ -24,8 +24,8 @@ def merge(self, **kwargs) -> None:
|
|
|
24
24
|
----------
|
|
25
25
|
**kwargs : dict
|
|
26
26
|
Parameters from merge_defaults class:
|
|
27
|
-
- method : str, default '
|
|
28
|
-
Merge algorithm: '
|
|
27
|
+
- method : str, default 'quality'
|
|
28
|
+
Merge algorithm: 'sensitivity', 'qt', 'nowarp', 'chunked', 'quality'
|
|
29
29
|
- min_samples : int, default 10
|
|
30
30
|
Minimum number of samples for consensus feature
|
|
31
31
|
- rt_tol : float, default 2.0
|
|
@@ -47,9 +47,11 @@ def merge(self, **kwargs) -> None:
|
|
|
47
47
|
|
|
48
48
|
Algorithm Guidelines
|
|
49
49
|
-------------------
|
|
50
|
-
-
|
|
50
|
+
- Quality: KD with post-processing quality control to reduce oversegmentation (RECOMMENDED DEFAULT)
|
|
51
|
+
Includes RT tolerance optimization, secondary clustering, and quality filtering
|
|
52
|
+
- Sensitivity: Best raw sensitivity, O(n log n), maximum feature detection
|
|
51
53
|
- QT: Thorough but slow O(n²), good for <1000 samples
|
|
52
|
-
-
|
|
54
|
+
- NoWarp: Memory efficient KD without RT warping for large datasets
|
|
53
55
|
- Chunked: Memory-optimized KD algorithm for very large datasets (>5000 samples)
|
|
54
56
|
Uses optimized partitioning for better memory management while maintaining
|
|
55
57
|
full cross-sample consensus feature detection.
|
|
@@ -67,9 +69,24 @@ def merge(self, **kwargs) -> None:
|
|
|
67
69
|
else:
|
|
68
70
|
self.logger.warning(f"Unknown parameter '{key}' ignored")
|
|
69
71
|
|
|
72
|
+
# Backward compatibility: Map old method names to new names
|
|
73
|
+
method_mapping = {
|
|
74
|
+
'kd': 'sensitivity',
|
|
75
|
+
'kd-nowarp': 'nowarp',
|
|
76
|
+
'kd_nowarp': 'nowarp',
|
|
77
|
+
'kd-strict': 'quality',
|
|
78
|
+
'kd_strict': 'quality',
|
|
79
|
+
'kdstrict': 'quality'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if params.method in method_mapping:
|
|
83
|
+
old_method = params.method
|
|
84
|
+
params.method = method_mapping[old_method]
|
|
85
|
+
self.logger.info(f"Method '{old_method}' is deprecated. Using '{params.method}' instead.")
|
|
86
|
+
|
|
70
87
|
# Validate method
|
|
71
|
-
if params.method not in ['
|
|
72
|
-
raise ValueError(f"Invalid method '{params.method}'. Must be one of: ['
|
|
88
|
+
if params.method not in ['sensitivity', 'qt', 'nowarp', 'chunked', 'quality']:
|
|
89
|
+
raise ValueError(f"Invalid method '{params.method}'. Must be one of: ['sensitivity', 'qt', 'nowarp', 'chunked', 'quality']")
|
|
73
90
|
|
|
74
91
|
# Persist last used params for diagnostics
|
|
75
92
|
try:
|
|
@@ -77,6 +94,15 @@ def merge(self, **kwargs) -> None:
|
|
|
77
94
|
except Exception:
|
|
78
95
|
self._merge_params_last = {}
|
|
79
96
|
|
|
97
|
+
# Store merge parameters in history
|
|
98
|
+
try:
|
|
99
|
+
if hasattr(self, 'store_history'):
|
|
100
|
+
self.store_history(['merge'], params.to_dict())
|
|
101
|
+
else:
|
|
102
|
+
self.logger.warning("History storage not available - parameters not saved to history")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
self.logger.warning(f"Failed to store merge parameters in history: {e}")
|
|
105
|
+
|
|
80
106
|
# Ensure feature maps are available for merging (regenerate if needed)
|
|
81
107
|
if len(self.features_maps) < len(self.samples_df):
|
|
82
108
|
self.features_maps = []
|
|
@@ -106,7 +132,7 @@ def merge(self, **kwargs) -> None:
|
|
|
106
132
|
cached_valid_adducts.add("?")
|
|
107
133
|
|
|
108
134
|
# Route to algorithm implementation
|
|
109
|
-
if params.method == '
|
|
135
|
+
if params.method == 'sensitivity':
|
|
110
136
|
consensus_map = _merge_kd(self, params)
|
|
111
137
|
# Extract consensus features
|
|
112
138
|
self._extract_consensus_features(consensus_map, params.min_samples, cached_adducts_df, cached_valid_adducts)
|
|
@@ -114,10 +140,13 @@ def merge(self, **kwargs) -> None:
|
|
|
114
140
|
consensus_map = _merge_qt(self, params)
|
|
115
141
|
# Extract consensus features
|
|
116
142
|
self._extract_consensus_features(consensus_map, params.min_samples, cached_adducts_df, cached_valid_adducts)
|
|
117
|
-
elif params.method == '
|
|
143
|
+
elif params.method == 'nowarp':
|
|
118
144
|
consensus_map = _merge_kd_nowarp(self, params)
|
|
119
145
|
# Extract consensus features
|
|
120
146
|
self._extract_consensus_features(consensus_map, params.min_samples, cached_adducts_df, cached_valid_adducts)
|
|
147
|
+
elif params.method == 'quality':
|
|
148
|
+
consensus_map = _merge_kd_strict(self, params)
|
|
149
|
+
# Note: _merge_kd_strict handles both consensus_df and consensus_mapping_df directly
|
|
121
150
|
elif params.method == 'chunked':
|
|
122
151
|
consensus_map = _merge_chunked(self, params, cached_adducts_df, cached_valid_adducts)
|
|
123
152
|
# Note: _merge_chunked populates consensus_df directly, no need to extract
|
|
@@ -209,6 +238,496 @@ def _merge_qt(self, params: merge_defaults) -> oms.ConsensusMap:
|
|
|
209
238
|
return consensus_map
|
|
210
239
|
|
|
211
240
|
|
|
241
|
+
def _merge_kd_strict(self, params: merge_defaults) -> oms.ConsensusMap:
|
|
242
|
+
"""
|
|
243
|
+
Quality merge: Standard KD algorithm with post-processing quality control.
|
|
244
|
+
|
|
245
|
+
This method combines the sensitivity of KD clustering with post-processing steps
|
|
246
|
+
to reduce oversegmentation while maintaining high-quality consensus features.
|
|
247
|
+
This is the recommended default method.
|
|
248
|
+
|
|
249
|
+
Post-processing features:
|
|
250
|
+
1. RT tolerance optimization (optional)
|
|
251
|
+
2. Secondary clustering for close features
|
|
252
|
+
3. Sample overlap validation
|
|
253
|
+
4. RT spread quality filtering
|
|
254
|
+
5. Chromatographic coherence validation
|
|
255
|
+
|
|
256
|
+
Additional parameters supported in params:
|
|
257
|
+
- optimize_rt_tol: bool - Enable RT tolerance optimization
|
|
258
|
+
- rt_tol_range: tuple - RT tolerance range for optimization (min, max)
|
|
259
|
+
- secondary_merge_rt_tol: float - Secondary merge RT tolerance (default: 0.5s)
|
|
260
|
+
- secondary_merge_mz_tol: float - Secondary merge m/z tolerance (default: 0.005)
|
|
261
|
+
- min_sample_overlap: float - Minimum sample overlap for merging (0.0-1.0, default: 0.8)
|
|
262
|
+
- max_rt_spread: float - Maximum RT spread allowed (default: 2x rt_tol)
|
|
263
|
+
- min_coherence: float - Minimum chromatographic coherence (default: 0.0, disabled)
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
# Check for RT tolerance optimization
|
|
267
|
+
optimize_rt_tol = getattr(params, 'optimize_rt_tol', False)
|
|
268
|
+
|
|
269
|
+
if optimize_rt_tol:
|
|
270
|
+
# Optimize RT tolerance first
|
|
271
|
+
optimal_rt_tol = _optimize_rt_tolerance(self, params)
|
|
272
|
+
self.logger.info(f"RT tolerance optimization: {params.rt_tol}s → {optimal_rt_tol}s")
|
|
273
|
+
# Create modified params with optimal RT tolerance
|
|
274
|
+
import copy
|
|
275
|
+
optimized_params = copy.deepcopy(params)
|
|
276
|
+
optimized_params.rt_tol = optimal_rt_tol
|
|
277
|
+
else:
|
|
278
|
+
optimized_params = params
|
|
279
|
+
|
|
280
|
+
# Phase 1: Standard KD clustering
|
|
281
|
+
self.logger.info("Initial KD clustering")
|
|
282
|
+
consensus_map = _merge_kd(self, optimized_params)
|
|
283
|
+
|
|
284
|
+
# Phase 2: Post-processing quality control
|
|
285
|
+
self.logger.info("Post-processing quality control")
|
|
286
|
+
consensus_map = _apply_kd_strict_postprocessing(self, consensus_map, optimized_params)
|
|
287
|
+
|
|
288
|
+
return consensus_map
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _optimize_rt_tolerance(self, params: merge_defaults) -> float:
|
|
292
|
+
"""
|
|
293
|
+
Optimize RT tolerance by testing different values and measuring oversegmentation.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
self: Study object
|
|
297
|
+
params: Merge parameters
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Optimal RT tolerance value
|
|
301
|
+
"""
|
|
302
|
+
rt_tol_range = getattr(params, 'rt_tol_range', (0.8, 2.0))
|
|
303
|
+
rt_tol_steps = getattr(params, 'rt_tol_steps', 5)
|
|
304
|
+
|
|
305
|
+
self.logger.info(f"Optimizing RT tolerance in range {rt_tol_range} with {rt_tol_steps} steps")
|
|
306
|
+
|
|
307
|
+
# Generate test values
|
|
308
|
+
test_rt_tols = [rt_tol_range[0] + i * (rt_tol_range[1] - rt_tol_range[0]) / (rt_tol_steps - 1)
|
|
309
|
+
for i in range(rt_tol_steps)]
|
|
310
|
+
|
|
311
|
+
best_rt_tol = params.rt_tol
|
|
312
|
+
best_score = float('inf')
|
|
313
|
+
|
|
314
|
+
# Store original features for restoration
|
|
315
|
+
original_consensus_df = getattr(self, 'consensus_df', pl.DataFrame())
|
|
316
|
+
original_consensus_mapping_df = getattr(self, 'consensus_mapping_df', pl.DataFrame())
|
|
317
|
+
|
|
318
|
+
for test_rt_tol in test_rt_tols:
|
|
319
|
+
try:
|
|
320
|
+
# Create test parameters
|
|
321
|
+
import copy
|
|
322
|
+
test_params = copy.deepcopy(params)
|
|
323
|
+
test_params.rt_tol = test_rt_tol
|
|
324
|
+
|
|
325
|
+
# Run KD merge with test parameters
|
|
326
|
+
test_consensus_map = _merge_kd(self, test_params)
|
|
327
|
+
|
|
328
|
+
# Extract consensus features temporarily for analysis
|
|
329
|
+
self._extract_consensus_features(test_consensus_map, test_params.min_samples)
|
|
330
|
+
|
|
331
|
+
if len(self.consensus_df) == 0:
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
# Calculate oversegmentation metrics
|
|
335
|
+
oversegmentation_score = _calculate_oversegmentation_score(self, test_rt_tol)
|
|
336
|
+
|
|
337
|
+
self.logger.debug(f"RT tol {test_rt_tol:.1f}s: {len(self.consensus_df)} features, score: {oversegmentation_score:.3f}")
|
|
338
|
+
|
|
339
|
+
# Lower score is better (less oversegmentation)
|
|
340
|
+
if oversegmentation_score < best_score:
|
|
341
|
+
best_score = oversegmentation_score
|
|
342
|
+
best_rt_tol = test_rt_tol
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
self.logger.warning(f"RT tolerance optimization failed for {test_rt_tol}s: {e}")
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
# Restore original consensus data
|
|
349
|
+
self.consensus_df = original_consensus_df
|
|
350
|
+
self.consensus_mapping_df = original_consensus_mapping_df
|
|
351
|
+
|
|
352
|
+
self.logger.info(f"Optimal RT tolerance: {best_rt_tol:.1f}s (score: {best_score:.3f})")
|
|
353
|
+
return best_rt_tol
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _calculate_oversegmentation_score(self, rt_tol: float) -> float:
|
|
357
|
+
"""
|
|
358
|
+
Calculate oversegmentation score based on feature density and RT spread metrics.
|
|
359
|
+
Lower scores indicate less oversegmentation.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
self: Study object
|
|
363
|
+
rt_tol: RT tolerance used
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Oversegmentation score (lower = better)
|
|
367
|
+
"""
|
|
368
|
+
if len(self.consensus_df) == 0:
|
|
369
|
+
return float('inf')
|
|
370
|
+
|
|
371
|
+
# Metric 1: Feature density (features per RT second)
|
|
372
|
+
rt_range = self.consensus_df['rt'].max() - self.consensus_df['rt'].min()
|
|
373
|
+
if rt_range <= 0:
|
|
374
|
+
return float('inf')
|
|
375
|
+
|
|
376
|
+
feature_density = len(self.consensus_df) / rt_range
|
|
377
|
+
|
|
378
|
+
# Metric 2: Average RT spread relative to tolerance
|
|
379
|
+
rt_spreads = (self.consensus_df['rt_max'] - self.consensus_df['rt_min'])
|
|
380
|
+
avg_rt_spread_ratio = rt_spreads.mean() / rt_tol if rt_tol > 0 else float('inf')
|
|
381
|
+
|
|
382
|
+
# Metric 3: Proportion of features with low sample counts (indicates fragmentation)
|
|
383
|
+
low_sample_features = len(self.consensus_df.filter(pl.col('number_samples') <= 5))
|
|
384
|
+
low_sample_ratio = low_sample_features / len(self.consensus_df)
|
|
385
|
+
|
|
386
|
+
# Metric 4: Number of features with excessive RT spread
|
|
387
|
+
excessive_spread_features = len(rt_spreads.filter(rt_spreads > rt_tol * 2))
|
|
388
|
+
excessive_spread_ratio = excessive_spread_features / len(self.consensus_df)
|
|
389
|
+
|
|
390
|
+
# Combined score (weighted combination)
|
|
391
|
+
oversegmentation_score = (
|
|
392
|
+
0.4 * (feature_density / 10.0) + # Normalize to reasonable scale
|
|
393
|
+
0.3 * avg_rt_spread_ratio +
|
|
394
|
+
0.2 * low_sample_ratio +
|
|
395
|
+
0.1 * excessive_spread_ratio
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return oversegmentation_score
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _apply_kd_strict_postprocessing(self, consensus_map: oms.ConsensusMap, params: merge_defaults) -> oms.ConsensusMap:
|
|
402
|
+
"""
|
|
403
|
+
Apply post-processing quality control to KD consensus map.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
consensus_map: Initial consensus map from KD
|
|
407
|
+
params: Merge parameters with kd-strict options
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Processed consensus map with reduced oversegmentation
|
|
411
|
+
"""
|
|
412
|
+
if consensus_map.size() == 0:
|
|
413
|
+
self.logger.warning("Empty consensus map provided to post-processing")
|
|
414
|
+
return consensus_map
|
|
415
|
+
|
|
416
|
+
self.logger.debug(f"Post-processing {consensus_map.size()} initial consensus features")
|
|
417
|
+
|
|
418
|
+
# Step 1: Extract initial consensus features
|
|
419
|
+
original_min_samples = params.min_samples
|
|
420
|
+
params.min_samples = 1 # Extract all features initially
|
|
421
|
+
|
|
422
|
+
self._extract_consensus_features(consensus_map, params.min_samples)
|
|
423
|
+
initial_feature_count = len(self.consensus_df)
|
|
424
|
+
|
|
425
|
+
if initial_feature_count == 0:
|
|
426
|
+
self.logger.warning("No consensus features extracted for post-processing")
|
|
427
|
+
params.min_samples = original_min_samples
|
|
428
|
+
return consensus_map
|
|
429
|
+
|
|
430
|
+
# Step 2: Secondary clustering for close features
|
|
431
|
+
secondary_merge_rt_tol = getattr(params, 'secondary_merge_rt_tol', 0.5)
|
|
432
|
+
secondary_merge_mz_tol = getattr(params, 'secondary_merge_mz_tol', 0.005)
|
|
433
|
+
|
|
434
|
+
self.logger.debug(f"Secondary clustering with RT≤{secondary_merge_rt_tol}s, m/z≤{secondary_merge_mz_tol}")
|
|
435
|
+
merged_features = _perform_secondary_clustering(self, secondary_merge_rt_tol, secondary_merge_mz_tol)
|
|
436
|
+
|
|
437
|
+
# Step 3: Sample overlap validation
|
|
438
|
+
min_sample_overlap = getattr(params, 'min_sample_overlap', 0.8)
|
|
439
|
+
if min_sample_overlap > 0:
|
|
440
|
+
self.logger.debug(f"Sample overlap validation (threshold: {min_sample_overlap})")
|
|
441
|
+
merged_features = _validate_sample_overlap(self, merged_features, min_sample_overlap)
|
|
442
|
+
|
|
443
|
+
# Step 4: RT spread quality filtering
|
|
444
|
+
if params.rt_tol is not None:
|
|
445
|
+
max_rt_spread = getattr(params, 'max_rt_spread', params.rt_tol * 2)
|
|
446
|
+
if max_rt_spread is not None:
|
|
447
|
+
self.logger.debug(f"RT spread filtering (max: {max_rt_spread:.1f}s)")
|
|
448
|
+
merged_features = _filter_rt_spread(self, merged_features, max_rt_spread)
|
|
449
|
+
else:
|
|
450
|
+
self.logger.debug("Skipping RT spread filtering - max_rt_spread is None")
|
|
451
|
+
else:
|
|
452
|
+
self.logger.debug("Skipping RT spread filtering - rt_tol is None")
|
|
453
|
+
|
|
454
|
+
# Step 5: Chromatographic coherence filtering (optional)
|
|
455
|
+
min_coherence = getattr(params, 'min_coherence', 0.0)
|
|
456
|
+
if min_coherence > 0:
|
|
457
|
+
self.logger.debug(f"Chromatographic coherence filtering (min: {min_coherence})")
|
|
458
|
+
merged_features = _filter_coherence(self, merged_features, min_coherence)
|
|
459
|
+
|
|
460
|
+
# Step 6: Rebuild consensus_df with filtered features and preserve mapping
|
|
461
|
+
original_mapping_df = self.consensus_mapping_df.clone() # Save original mapping
|
|
462
|
+
self.consensus_df = pl.DataFrame(merged_features, strict=False)
|
|
463
|
+
|
|
464
|
+
# Step 7: Apply original min_samples filter
|
|
465
|
+
params.min_samples = original_min_samples
|
|
466
|
+
if params.min_samples > 1:
|
|
467
|
+
l1 = len(self.consensus_df)
|
|
468
|
+
self.consensus_df = self.consensus_df.filter(
|
|
469
|
+
pl.col("number_samples") >= params.min_samples
|
|
470
|
+
)
|
|
471
|
+
filtered_count = l1 - len(self.consensus_df)
|
|
472
|
+
if filtered_count > 0:
|
|
473
|
+
self.logger.debug(f"Filtered {filtered_count} features below min_samples threshold ({params.min_samples})")
|
|
474
|
+
|
|
475
|
+
# Step 8: Update consensus_mapping_df to match final consensus_df
|
|
476
|
+
if len(self.consensus_df) > 0 and len(original_mapping_df) > 0:
|
|
477
|
+
valid_consensus_ids = set(self.consensus_df['consensus_uid'].to_list())
|
|
478
|
+
self.consensus_mapping_df = original_mapping_df.filter(
|
|
479
|
+
pl.col('consensus_uid').is_in(list(valid_consensus_ids))
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
self.consensus_mapping_df = pl.DataFrame()
|
|
483
|
+
|
|
484
|
+
final_feature_count = len(self.consensus_df)
|
|
485
|
+
reduction_pct = ((initial_feature_count - final_feature_count) / initial_feature_count * 100) if initial_feature_count > 0 else 0
|
|
486
|
+
|
|
487
|
+
self.logger.info(f"Post-processing complete: {initial_feature_count} → {final_feature_count} features ({reduction_pct:.1f}% reduction)")
|
|
488
|
+
|
|
489
|
+
# Create a new consensus map for compatibility (the processed data is in consensus_df)
|
|
490
|
+
processed_consensus_map = oms.ConsensusMap()
|
|
491
|
+
return processed_consensus_map
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _perform_secondary_clustering(self, rt_tol: float, mz_tol: float) -> list:
|
|
495
|
+
"""
|
|
496
|
+
Perform secondary clustering to merge very close features.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
rt_tol: RT tolerance for secondary clustering
|
|
500
|
+
mz_tol: m/z tolerance for secondary clustering
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
List of merged consensus feature dictionaries
|
|
504
|
+
"""
|
|
505
|
+
if len(self.consensus_df) == 0:
|
|
506
|
+
return []
|
|
507
|
+
|
|
508
|
+
# Convert consensus_df to list of dictionaries for clustering
|
|
509
|
+
consensus_features = []
|
|
510
|
+
for i, row in enumerate(self.consensus_df.iter_rows(named=True)):
|
|
511
|
+
consensus_features.append(dict(row))
|
|
512
|
+
|
|
513
|
+
# Use Union-Find for efficient clustering
|
|
514
|
+
class UnionFind:
|
|
515
|
+
def __init__(self, n):
|
|
516
|
+
self.parent = list(range(n))
|
|
517
|
+
self.rank = [0] * n
|
|
518
|
+
|
|
519
|
+
def find(self, x):
|
|
520
|
+
if self.parent[x] != x:
|
|
521
|
+
self.parent[x] = self.find(self.parent[x])
|
|
522
|
+
return self.parent[x]
|
|
523
|
+
|
|
524
|
+
def union(self, x, y):
|
|
525
|
+
px, py = self.find(x), self.find(y)
|
|
526
|
+
if px == py:
|
|
527
|
+
return
|
|
528
|
+
if self.rank[px] < self.rank[py]:
|
|
529
|
+
px, py = py, px
|
|
530
|
+
self.parent[py] = px
|
|
531
|
+
if self.rank[px] == self.rank[py]:
|
|
532
|
+
self.rank[px] += 1
|
|
533
|
+
|
|
534
|
+
n_features = len(consensus_features)
|
|
535
|
+
uf = UnionFind(n_features)
|
|
536
|
+
|
|
537
|
+
# Find features to merge based on proximity
|
|
538
|
+
merge_count = 0
|
|
539
|
+
for i in range(n_features):
|
|
540
|
+
for j in range(i + 1, n_features):
|
|
541
|
+
feat_i = consensus_features[i]
|
|
542
|
+
feat_j = consensus_features[j]
|
|
543
|
+
|
|
544
|
+
rt_diff = abs(feat_i['rt'] - feat_j['rt'])
|
|
545
|
+
mz_diff = abs(feat_i['mz'] - feat_j['mz'])
|
|
546
|
+
|
|
547
|
+
if rt_diff <= rt_tol and mz_diff <= mz_tol:
|
|
548
|
+
uf.union(i, j)
|
|
549
|
+
merge_count += 1
|
|
550
|
+
|
|
551
|
+
# Group features by their root
|
|
552
|
+
groups_by_root = defaultdict(list)
|
|
553
|
+
for i in range(n_features):
|
|
554
|
+
root = uf.find(i)
|
|
555
|
+
groups_by_root[root].append(consensus_features[i])
|
|
556
|
+
|
|
557
|
+
# Merge features within each group
|
|
558
|
+
merged_features = []
|
|
559
|
+
for group in groups_by_root.values():
|
|
560
|
+
if len(group) == 1:
|
|
561
|
+
# Single feature - keep as is
|
|
562
|
+
merged_features.append(group[0])
|
|
563
|
+
else:
|
|
564
|
+
# Multiple features - merge them
|
|
565
|
+
merged_feature = _merge_feature_group(group)
|
|
566
|
+
merged_features.append(merged_feature)
|
|
567
|
+
|
|
568
|
+
self.logger.debug(f"Secondary clustering: {n_features} → {len(merged_features)} features ({n_features - len(merged_features)} merged)")
|
|
569
|
+
return merged_features
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _merge_feature_group(feature_group: list) -> dict:
|
|
573
|
+
"""
|
|
574
|
+
Merge a group of similar consensus features into one.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
feature_group: List of consensus feature dictionaries to merge
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
Merged consensus feature dictionary
|
|
581
|
+
"""
|
|
582
|
+
if not feature_group:
|
|
583
|
+
return {}
|
|
584
|
+
|
|
585
|
+
if len(feature_group) == 1:
|
|
586
|
+
return feature_group[0]
|
|
587
|
+
|
|
588
|
+
# Use the feature with highest sample count as base
|
|
589
|
+
base_feature = max(feature_group, key=lambda f: f.get('number_samples', 0))
|
|
590
|
+
merged = base_feature.copy()
|
|
591
|
+
|
|
592
|
+
# Aggregate numeric statistics
|
|
593
|
+
rt_values = [f['rt'] for f in feature_group if f.get('rt') is not None]
|
|
594
|
+
mz_values = [f['mz'] for f in feature_group if f.get('mz') is not None]
|
|
595
|
+
sample_counts = [f.get('number_samples', 0) for f in feature_group]
|
|
596
|
+
intensities = [f.get('inty_mean', 0) for f in feature_group if f.get('inty_mean') is not None]
|
|
597
|
+
|
|
598
|
+
# Update merged feature statistics
|
|
599
|
+
if rt_values:
|
|
600
|
+
merged['rt'] = float(np.mean(rt_values))
|
|
601
|
+
merged['rt_min'] = min([f.get('rt_min', f['rt']) for f in feature_group])
|
|
602
|
+
merged['rt_max'] = max([f.get('rt_max', f['rt']) for f in feature_group])
|
|
603
|
+
merged['rt_mean'] = float(np.mean(rt_values))
|
|
604
|
+
|
|
605
|
+
if mz_values:
|
|
606
|
+
merged['mz'] = float(np.mean(mz_values))
|
|
607
|
+
merged['mz_min'] = min([f.get('mz_min', f['mz']) for f in feature_group])
|
|
608
|
+
merged['mz_max'] = max([f.get('mz_max', f['mz']) for f in feature_group])
|
|
609
|
+
merged['mz_mean'] = float(np.mean(mz_values))
|
|
610
|
+
|
|
611
|
+
# Use maximum sample count (features might be detected in overlapping but different samples)
|
|
612
|
+
merged['number_samples'] = max(sample_counts)
|
|
613
|
+
|
|
614
|
+
# Use weighted average intensity (by sample count)
|
|
615
|
+
if intensities and sample_counts:
|
|
616
|
+
total_weight = sum(sample_counts)
|
|
617
|
+
if total_weight > 0:
|
|
618
|
+
weighted_intensity = sum(inty * count for inty, count in zip(intensities, sample_counts)) / total_weight
|
|
619
|
+
merged['inty_mean'] = float(weighted_intensity)
|
|
620
|
+
|
|
621
|
+
# Aggregate chromatographic quality metrics if available
|
|
622
|
+
coherence_values = [f.get('chrom_coherence_mean', 0) for f in feature_group if f.get('chrom_coherence_mean') is not None]
|
|
623
|
+
prominence_values = [f.get('chrom_prominence_mean', 0) for f in feature_group if f.get('chrom_prominence_mean') is not None]
|
|
624
|
+
|
|
625
|
+
if coherence_values:
|
|
626
|
+
merged['chrom_coherence_mean'] = float(np.mean(coherence_values))
|
|
627
|
+
if prominence_values:
|
|
628
|
+
merged['chrom_prominence_mean'] = float(np.mean(prominence_values))
|
|
629
|
+
|
|
630
|
+
# Merge MS2 counts
|
|
631
|
+
ms2_counts = [f.get('number_ms2', 0) for f in feature_group]
|
|
632
|
+
merged['number_ms2'] = sum(ms2_counts)
|
|
633
|
+
|
|
634
|
+
# Keep the best quality score
|
|
635
|
+
quality_scores = [f.get('quality', 1.0) for f in feature_group if f.get('quality') is not None]
|
|
636
|
+
if quality_scores:
|
|
637
|
+
merged['quality'] = max(quality_scores)
|
|
638
|
+
|
|
639
|
+
return merged
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _validate_sample_overlap(self, features: list, min_overlap: float) -> list:
|
|
643
|
+
"""
|
|
644
|
+
Validate that merged features have sufficient sample overlap.
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
features: List of consensus feature dictionaries
|
|
648
|
+
min_overlap: Minimum sample overlap ratio (0.0-1.0)
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
List of validated features
|
|
652
|
+
"""
|
|
653
|
+
# This is a placeholder for sample overlap validation
|
|
654
|
+
# Implementation would require access to which samples each feature appears in
|
|
655
|
+
# For now, we'll use a simple heuristic based on feature statistics
|
|
656
|
+
|
|
657
|
+
validated_features = []
|
|
658
|
+
for feature in features:
|
|
659
|
+
# Simple validation based on RT spread and sample count ratio
|
|
660
|
+
rt_spread = feature.get('rt_max', feature['rt']) - feature.get('rt_min', feature['rt'])
|
|
661
|
+
sample_count = feature.get('number_samples', 1)
|
|
662
|
+
|
|
663
|
+
# Features with very tight RT spread and high sample counts are more reliable
|
|
664
|
+
if rt_spread <= 2.0 or sample_count >= 10: # More permissive validation
|
|
665
|
+
validated_features.append(feature)
|
|
666
|
+
else:
|
|
667
|
+
# Could implement more sophisticated sample overlap checking here
|
|
668
|
+
validated_features.append(feature) # Keep for now
|
|
669
|
+
|
|
670
|
+
return validated_features
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def _filter_rt_spread(self, features: list, max_rt_spread: float) -> list:
|
|
674
|
+
"""
|
|
675
|
+
Filter out features with excessive RT spread.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
features: List of consensus feature dictionaries
|
|
679
|
+
max_rt_spread: Maximum allowed RT spread in seconds
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
List of filtered features
|
|
683
|
+
"""
|
|
684
|
+
filtered_features = []
|
|
685
|
+
filtered_count = 0
|
|
686
|
+
|
|
687
|
+
for feature in features:
|
|
688
|
+
rt_min = feature.get('rt_min', feature['rt'])
|
|
689
|
+
rt_max = feature.get('rt_max', feature['rt'])
|
|
690
|
+
rt_spread = rt_max - rt_min
|
|
691
|
+
|
|
692
|
+
if rt_spread <= max_rt_spread:
|
|
693
|
+
filtered_features.append(feature)
|
|
694
|
+
else:
|
|
695
|
+
filtered_count += 1
|
|
696
|
+
|
|
697
|
+
if filtered_count > 0:
|
|
698
|
+
self.logger.debug(f"Filtered {filtered_count} features with excessive RT spread (>{max_rt_spread:.1f}s)")
|
|
699
|
+
|
|
700
|
+
return filtered_features
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _filter_coherence(self, features: list, min_coherence: float) -> list:
|
|
704
|
+
"""
|
|
705
|
+
Filter out features with low chromatographic coherence.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
features: List of consensus feature dictionaries
|
|
709
|
+
min_coherence: Minimum chromatographic coherence score
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
List of filtered features
|
|
713
|
+
"""
|
|
714
|
+
filtered_features = []
|
|
715
|
+
filtered_count = 0
|
|
716
|
+
|
|
717
|
+
for feature in features:
|
|
718
|
+
coherence = feature.get('chrom_coherence_mean', 1.0) # Default to high coherence if missing
|
|
719
|
+
|
|
720
|
+
if coherence >= min_coherence:
|
|
721
|
+
filtered_features.append(feature)
|
|
722
|
+
else:
|
|
723
|
+
filtered_count += 1
|
|
724
|
+
|
|
725
|
+
if filtered_count > 0:
|
|
726
|
+
self.logger.debug(f"Filtered {filtered_count} features with low coherence (<{min_coherence})")
|
|
727
|
+
|
|
728
|
+
return filtered_features
|
|
729
|
+
|
|
730
|
+
|
|
212
731
|
def _merge_kd_nowarp(self, params: merge_defaults) -> oms.ConsensusMap:
|
|
213
732
|
"""KD-tree based merge without RT warping"""
|
|
214
733
|
|
|
@@ -470,11 +989,19 @@ def _merge_chunk_results(self, chunk_consensus_maps: list, params: merge_default
|
|
|
470
989
|
b = chunk_consensus_list[j]
|
|
471
990
|
if a['chunk_idx'] == b['chunk_idx']:
|
|
472
991
|
continue
|
|
473
|
-
|
|
992
|
+
|
|
993
|
+
# Primary check: centroid distance (strict)
|
|
474
994
|
centroid_close = (abs(a['rt']-b['rt']) <= rt_tol and abs(a['mz']-b['mz']) <= mz_tol)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
995
|
+
|
|
996
|
+
# Secondary check: interval overlap (more conservative)
|
|
997
|
+
# Only allow interval overlap if centroids are reasonably close (within 2x tolerance)
|
|
998
|
+
centroids_reasonable = (abs(a['rt']-b['rt']) <= 2 * rt_tol and abs(a['mz']-b['mz']) <= 2 * mz_tol)
|
|
999
|
+
if centroids_reasonable:
|
|
1000
|
+
rt_overlap = (a['rt_min'] - rt_tol/2) <= (b['rt_max'] + rt_tol/2) and (b['rt_min'] - rt_tol/2) <= (a['rt_max'] + rt_tol/2)
|
|
1001
|
+
mz_overlap = (a['mz_min'] - mz_tol/2) <= (b['mz_max'] + mz_tol/2) and (b['mz_min'] - mz_tol/2) <= (a['mz_max'] + mz_tol/2)
|
|
1002
|
+
else:
|
|
1003
|
+
rt_overlap = mz_overlap = False
|
|
1004
|
+
|
|
478
1005
|
if centroid_close or (rt_overlap and mz_overlap):
|
|
479
1006
|
uf.union(i,j)
|
|
480
1007
|
|
|
@@ -611,6 +1138,17 @@ def _merge_chunk_results(self, chunk_consensus_maps: list, params: merge_default
|
|
|
611
1138
|
cached_adducts_df=cached_adducts_df,
|
|
612
1139
|
cached_valid_adducts=cached_valid_adducts,
|
|
613
1140
|
)
|
|
1141
|
+
|
|
1142
|
+
# Validate RT spread doesn't exceed tolerance (with some flexibility for chunked merge)
|
|
1143
|
+
rt_spread = metadata.get('rt_max', 0) - metadata.get('rt_min', 0)
|
|
1144
|
+
max_allowed_spread = params.rt_tol * 2 # Allow 2x tolerance for chunked method
|
|
1145
|
+
|
|
1146
|
+
if rt_spread > max_allowed_spread:
|
|
1147
|
+
# Skip consensus features with excessive RT spread
|
|
1148
|
+
self.logger.debug(f"Skipping consensus feature {consensus_uid_counter} with RT spread {rt_spread:.3f}s > {max_allowed_spread:.3f}s")
|
|
1149
|
+
consensus_uid_counter += 1
|
|
1150
|
+
continue
|
|
1151
|
+
|
|
614
1152
|
consensus_metadata.append(metadata)
|
|
615
1153
|
|
|
616
1154
|
# Build mapping rows (deduplicated)
|
|
@@ -689,8 +1227,8 @@ def _calculate_consensus_statistics(study_obj, consensus_uid: int, feature_data_
|
|
|
689
1227
|
inty_values = np.array([fd.get("inty", 0) for fd in feature_data_list if fd.get("inty") is not None])
|
|
690
1228
|
coherence_values = np.array([fd.get("chrom_coherence", 0) for fd in feature_data_list if fd.get("chrom_coherence") is not None])
|
|
691
1229
|
prominence_values = np.array([fd.get("chrom_prominence", 0) for fd in feature_data_list if fd.get("chrom_prominence") is not None])
|
|
692
|
-
prominence_scaled_values = np.array([fd.get("
|
|
693
|
-
height_scaled_values = np.array([fd.get("
|
|
1230
|
+
prominence_scaled_values = np.array([fd.get("chrom_height_scaled", 0) for fd in feature_data_list if fd.get("chrom_height_scaled") is not None])
|
|
1231
|
+
height_scaled_values = np.array([fd.get("chrom_prominence_scaled", 0) for fd in feature_data_list if fd.get("chrom_prominence_scaled") is not None])
|
|
694
1232
|
iso_values = np.array([fd.get("iso", 0) for fd in feature_data_list if fd.get("iso") is not None])
|
|
695
1233
|
charge_values = np.array([fd.get("charge", 0) for fd in feature_data_list if fd.get("charge") is not None])
|
|
696
1234
|
|
|
@@ -1006,16 +1544,16 @@ def _extract_consensus_features(self, consensus_map, min_samples, cached_adducts
|
|
|
1006
1544
|
)
|
|
1007
1545
|
prominence_scaled_values = np.array(
|
|
1008
1546
|
[
|
|
1009
|
-
fd.get("
|
|
1547
|
+
fd.get("chrom_height_scaled", 0)
|
|
1010
1548
|
for fd in feature_data_list
|
|
1011
|
-
if fd.get("
|
|
1549
|
+
if fd.get("chrom_height_scaled") is not None
|
|
1012
1550
|
],
|
|
1013
1551
|
)
|
|
1014
1552
|
height_scaled_values = np.array(
|
|
1015
1553
|
[
|
|
1016
|
-
fd.get("
|
|
1554
|
+
fd.get("chrom_prominence_scaled", 0)
|
|
1017
1555
|
for fd in feature_data_list
|
|
1018
|
-
if fd.get("
|
|
1556
|
+
if fd.get("chrom_prominence_scaled") is not None
|
|
1019
1557
|
],
|
|
1020
1558
|
)
|
|
1021
1559
|
iso_values = np.array(
|
masster/study/plot.py
CHANGED
|
@@ -310,8 +310,22 @@ def plot_alignment(
|
|
|
310
310
|
max_inty = sample_data.select(pl.col("inty").max()).item() or 1
|
|
311
311
|
|
|
312
312
|
# Get sample information
|
|
313
|
-
sample_name = str(sample)
|
|
314
313
|
sample_uid = sample if sample_col == "sample_uid" else sample_data.select(pl.col("sample_uid")).item() if "sample_uid" in sample_data.columns else sample
|
|
314
|
+
|
|
315
|
+
# Try to get actual sample name from samples_df if available
|
|
316
|
+
sample_name = str(sample) # fallback
|
|
317
|
+
if hasattr(self, "samples_df") and self.samples_df is not None and sample_uid is not None:
|
|
318
|
+
try:
|
|
319
|
+
sample_name_result = (
|
|
320
|
+
self.samples_df.filter(pl.col("sample_uid") == sample_uid)
|
|
321
|
+
.select("sample_name")
|
|
322
|
+
.to_series()
|
|
323
|
+
)
|
|
324
|
+
if len(sample_name_result) > 0 and sample_name_result[0] is not None:
|
|
325
|
+
sample_name = str(sample_name_result[0])
|
|
326
|
+
except Exception:
|
|
327
|
+
# Keep the fallback value
|
|
328
|
+
pass
|
|
315
329
|
|
|
316
330
|
# Select columns to process
|
|
317
331
|
cols_to_select = ["rt", "mz", "inty"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: masster
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.17
|
|
4
4
|
Summary: Mass spectrometry data analysis package
|
|
5
5
|
Project-URL: homepage, https://github.com/zamboni-lab/masster
|
|
6
6
|
Project-URL: repository, https://github.com/zamboni-lab/masster
|
|
@@ -767,7 +767,8 @@ study.integrate()
|
|
|
767
767
|
# export results
|
|
768
768
|
study.export_mgf()
|
|
769
769
|
study.export_mztab()
|
|
770
|
-
study.
|
|
770
|
+
study.export_xlsx()
|
|
771
|
+
study.export_parquet()
|
|
771
772
|
|
|
772
773
|
# Save the study to .study5
|
|
773
774
|
study.save()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
masster/__init__.py,sha256=HHjKhCjkAc98LhoQfu4C6L-W2vfTEc1iXaPTxxcl_4A,800
|
|
2
|
-
masster/_version.py,sha256=
|
|
2
|
+
masster/_version.py,sha256=A-Vx5wjFdgUfquBN1kWTW90q7wTOwZx-uonA2Xl-IWc,257
|
|
3
3
|
masster/chromatogram.py,sha256=iYpdv8C17zVnlWvOFgAn9ns2uFGiF-GgoYf5QVVAbHs,19319
|
|
4
4
|
masster/logger.py,sha256=W50V_uh8RSYwGxDrDFhOuj5jpu2tKJyt_16lMw9kQwA,14755
|
|
5
5
|
masster/spectrum.py,sha256=_upC_g2N9gwTaflXAugs9pSXpKUmzbIehofDordk7WI,47718
|
|
@@ -43,9 +43,9 @@ masster/study/h5.py,sha256=LiVGUAtULyPpZIUmKVJSaV38huJb8FsKOUWBOqiv0QU,82363
|
|
|
43
43
|
masster/study/helpers.py,sha256=M5_q8O5tuFchKPW04PTuj3X335lDA2VZqcs4D8ZQJEk,158604
|
|
44
44
|
masster/study/id.py,sha256=6NUBBKZCFOU1wlDKM0eXQeOIStSZCRNJ_3x7ZaIHzmM,55263
|
|
45
45
|
masster/study/load.py,sha256=CQQY_7BzagE3oQTdDlqNyfuMdVWIAft-M4a2WCFnxp0,70695
|
|
46
|
-
masster/study/merge.py,sha256
|
|
46
|
+
masster/study/merge.py,sha256=-gc-255NTKxkJZcIRl1wqQsMMi0m8zoZ10BkGsINFDc,92012
|
|
47
47
|
masster/study/parameters.py,sha256=0elaF7YspTsB7qyajWAbRNL2VfKlGz5GJLifmO8IGkk,3276
|
|
48
|
-
masster/study/plot.py,sha256=
|
|
48
|
+
masster/study/plot.py,sha256=SimX-IlqISEItAnTBsx4xsdYHRAevfN41cCENVns1lw,88236
|
|
49
49
|
masster/study/processing.py,sha256=pm98FrQHoM3ov6qmjKuVN9h2KBhGgCLEZCRS7zpmJFM,41104
|
|
50
50
|
masster/study/save.py,sha256=YCvp4xhnG16sNXaT2mFDBoCrIMub0Es61B97qLo0maw,6705
|
|
51
51
|
masster/study/study.py,sha256=LO_hbJOOCZzeA3uterPKImFgPG6fCNQKMSVMtEwW3DU,38815
|
|
@@ -60,7 +60,7 @@ masster/study/defaults/find_ms2_def.py,sha256=RL0DFG41wQ05U8UQKUGr3vzSl3mU0m0knQ
|
|
|
60
60
|
masster/study/defaults/identify_def.py,sha256=96rxoCAPQj_yX-3mRoD2LTkTLJgG27eJQqwarLv5jL0,10580
|
|
61
61
|
masster/study/defaults/integrate_chrom_def.py,sha256=0MNIWGTjty-Zu-NTQsIweuj3UVqEY3x1x8pK0mPwYak,7264
|
|
62
62
|
masster/study/defaults/integrate_def.py,sha256=Vf4SAzdBfnsSZ3IRaF0qZvWu3gMDPHdgPfMYoPKeWv8,7246
|
|
63
|
-
masster/study/defaults/merge_def.py,sha256=
|
|
63
|
+
masster/study/defaults/merge_def.py,sha256=Q31JwAaVGgVPEVIsiyeiOsF97c48IKe48HXuqh-sA_k,13189
|
|
64
64
|
masster/study/defaults/study_def.py,sha256=h8dYbi9xv0sesCSQik49Z53IkskMmNtW6ixl7it5pL0,16033
|
|
65
65
|
masster/wizard/README.md,sha256=mL1A3YWJZOefpJ6D0-HqGLkVRmUlOpwyVFdvJBeeoZM,14149
|
|
66
66
|
masster/wizard/__init__.py,sha256=A9GHQvkq4lSRIA8V6AKB-TJy8s_npH8i1baUGdkw_is,364
|
|
@@ -68,8 +68,8 @@ masster/wizard/example.py,sha256=xEZFTH9UZ8HKOm6s3JL8Js0Uw5ChnISWBHSZCL32vsM,798
|
|
|
68
68
|
masster/wizard/test_structure.py,sha256=h88gsYYCG6iDRjqPZC_r1H1T8y79j0E-K6OrwuHaSCU,1586
|
|
69
69
|
masster/wizard/test_wizard.py,sha256=CMp1cpjH3iYYC5Fy6puF_K0kfwwk3bgOsSbUGW-t7Xk,8986
|
|
70
70
|
masster/wizard/wizard.py,sha256=jMLHy4cXgNEE_-vshFmA7BNEByhfA6tV7O91jhiMYuw,48054
|
|
71
|
-
masster-0.4.
|
|
72
|
-
masster-0.4.
|
|
73
|
-
masster-0.4.
|
|
74
|
-
masster-0.4.
|
|
75
|
-
masster-0.4.
|
|
71
|
+
masster-0.4.17.dist-info/METADATA,sha256=uIdQNkAXQQzMkcVM53y_pUBZPzwqOx0lxGW8nmB1lz8,44207
|
|
72
|
+
masster-0.4.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
73
|
+
masster-0.4.17.dist-info/entry_points.txt,sha256=ZHguQ_vPmdbpqq2uGtmEOLJfgP-DQ1T0c07Lxh30wc8,58
|
|
74
|
+
masster-0.4.17.dist-info/licenses/LICENSE,sha256=bx5iLIKjgAdYQ7sISn7DsfHRKkoCUm1154sJJKhgqnU,35184
|
|
75
|
+
masster-0.4.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|