MaldiAMRKit 0.20.0__tar.gz → 0.22.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0/MaldiAMRKit.egg-info}/PKG-INFO +2 -2
  2. {maldiamrkit-0.20.0/MaldiAMRKit.egg-info → maldiamrkit-0.22.0}/PKG-INFO +2 -2
  3. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/README.md +1 -1
  4. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/__init__.py +1 -1
  5. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/alignment/peak_align.py +14 -9
  6. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/alignment/raw_warping.py +174 -29
  7. maldiamrkit-0.22.0/maldiamrkit/alignment/strategies.py +988 -0
  8. maldiamrkit-0.22.0/maldiamrkit/alignment/warping.py +628 -0
  9. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/dataset_layouts.py +74 -3
  10. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/input_layouts.py +70 -5
  11. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/site_info.py +13 -0
  12. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/detection/peaklist.py +17 -0
  13. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/monitor.py +57 -9
  14. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/evaluation/splitting.py +24 -2
  15. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/similarity/metrics.py +139 -23
  16. maldiamrkit-0.22.0/maldiamrkit/similarity/pairwise.py +230 -0
  17. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/susceptibility/mic_encoder.py +28 -2
  18. maldiamrkit-0.20.0/maldiamrkit/alignment/strategies.py +0 -700
  19. maldiamrkit-0.20.0/maldiamrkit/alignment/warping.py +0 -370
  20. maldiamrkit-0.20.0/maldiamrkit/similarity/pairwise.py +0 -159
  21. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/LICENSE +0 -0
  22. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/MaldiAMRKit.egg-info/SOURCES.txt +0 -0
  23. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/MaldiAMRKit.egg-info/dependency_links.txt +0 -0
  24. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/MaldiAMRKit.egg-info/entry_points.txt +0 -0
  25. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/MaldiAMRKit.egg-info/requires.txt +0 -0
  26. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/MaldiAMRKit.egg-info/top_level.txt +0 -0
  27. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/_compat.py +0 -0
  28. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/_registry.py +0 -0
  29. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/alignment/__init__.py +0 -0
  30. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/cli.py +0 -0
  31. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/__init__.py +0 -0
  32. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/README.md +0 -0
  33. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v1.0.yaml +0 -0
  34. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v1.1.yaml +0 -0
  35. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v1.2.yaml +0 -0
  36. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v1.3.yaml +0 -0
  37. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v10.0.yaml +0 -0
  38. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v11.0.yaml +0 -0
  39. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v12.0.yaml +0 -0
  40. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v13.1.yaml +0 -0
  41. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v14.0.yaml +0 -0
  42. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v15.0.yaml +0 -0
  43. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v16.0.yaml +0 -0
  44. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v2.0.yaml +0 -0
  45. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v4.0.yaml +0 -0
  46. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v5.0.yaml +0 -0
  47. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v6.0.yaml +0 -0
  48. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v7.1.yaml +0 -0
  49. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v8.0.yaml +0 -0
  50. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v8.1.yaml +0 -0
  51. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/eucast/eucast_v9.0.yaml +0 -0
  52. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/breakpoints/example.yaml +0 -0
  53. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/builder.py +0 -0
  54. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/duplicates.py +0 -0
  55. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/data/loader.py +0 -0
  56. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/dataset.py +0 -0
  57. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/detection/__init__.py +0 -0
  58. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/detection/peak_detector.py +0 -0
  59. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/differential/__init__.py +0 -0
  60. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/differential/analysis.py +0 -0
  61. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/differential/plots.py +0 -0
  62. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/differential/stats.py +0 -0
  63. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/__init__.py +0 -0
  64. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/pca_drift.py +0 -0
  65. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/peak_drift.py +0 -0
  66. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/plots.py +0 -0
  67. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/drift/reference.py +0 -0
  68. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/evaluation/__init__.py +0 -0
  69. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/evaluation/metrics.py +0 -0
  70. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/evaluation/mic_regression.py +0 -0
  71. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/filters.py +0 -0
  72. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/io/__init__.py +0 -0
  73. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/io/mic.py +0 -0
  74. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/io/readers.py +0 -0
  75. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/io/writers.py +0 -0
  76. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/__init__.py +0 -0
  77. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/binning.py +0 -0
  78. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/merging.py +0 -0
  79. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/pipeline.py +0 -0
  80. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/preprocessing_pipeline.py +0 -0
  81. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/quality.py +0 -0
  82. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/preprocessing/transformers.py +0 -0
  83. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/py.typed +0 -0
  84. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/similarity/__init__.py +0 -0
  85. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/similarity/clustering.py +0 -0
  86. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/similarity/plots.py +0 -0
  87. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/spectrum.py +0 -0
  88. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/susceptibility/__init__.py +0 -0
  89. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/susceptibility/breakpoint.py +0 -0
  90. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/susceptibility/label_encoder.py +0 -0
  91. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/__init__.py +0 -0
  92. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/_common.py +0 -0
  93. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/alignment_plots.py +0 -0
  94. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/exploratory_plots.py +0 -0
  95. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/peak_plots.py +0 -0
  96. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/maldiamrkit/visualization/spectrum_plots.py +0 -0
  97. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/pyproject.toml +0 -0
  98. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/requirements-batch.txt +0 -0
  99. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/requirements-dev.txt +0 -0
  100. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/requirements-docs.txt +0 -0
  101. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/requirements-formats.txt +0 -0
  102. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/requirements.txt +0 -0
  103. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/setup.cfg +0 -0
  104. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/tests/test_compat.py +0 -0
  105. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/tests/test_numerical_edge_cases.py +0 -0
  106. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/tests/test_peak_detector.py +0 -0
  107. {maldiamrkit-0.20.0 → maldiamrkit-0.22.0}/tests/test_peaklist.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MaldiAMRKit
3
- Version: 0.20.0
3
+ Version: 0.22.0
4
4
  Summary: A comprehensive toolkit for MALDI-TOF mass spectrometry data preprocessing for antimicrobial resistance (AMR) prediction purposes
5
5
  Author-email: Ettore Rocchi <ettore.rocchi3@unibo.it>
6
6
  License-Expression: MIT
@@ -140,7 +140,7 @@ pip install -e .[dev]
140
140
  - **Replicate Merging**: Mean/median/weighted merging with correlation-based outlier detection
141
141
 
142
142
  ### Alignment & Detection
143
- - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra
143
+ - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra. Spectra are first shifted and then aligned to the reference of the shifted spectra, a non-rigid warp is kept only when the matched peaks support it, and resampling preserves the total intensity; spectra of several species are best aligned one species at a time
144
144
  - **Peak Detection**: Local maxima and persistent homology methods
145
145
  - **Peak Sets**: `PeakSet` / `PeakList` represent each spectrum as a variable-length `(m/z, intensity)` set for peak-set models, via `MaldiPeakDetector.transform_peaklist()` on binned spectra or `create_peakset_input()` at full m/z resolution; extraction is stateless per spectrum, so results are cacheable without leakage
146
146
  - **Fit-Free Peak Alignment**: `align_peaks()` warps a peak set onto caller-supplied reference peaks, so the reference stays fitted on the training split
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MaldiAMRKit
3
- Version: 0.20.0
3
+ Version: 0.22.0
4
4
  Summary: A comprehensive toolkit for MALDI-TOF mass spectrometry data preprocessing for antimicrobial resistance (AMR) prediction purposes
5
5
  Author-email: Ettore Rocchi <ettore.rocchi3@unibo.it>
6
6
  License-Expression: MIT
@@ -140,7 +140,7 @@ pip install -e .[dev]
140
140
  - **Replicate Merging**: Mean/median/weighted merging with correlation-based outlier detection
141
141
 
142
142
  ### Alignment & Detection
143
- - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra
143
+ - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra. Spectra are first shifted and then aligned to the reference of the shifted spectra, a non-rigid warp is kept only when the matched peaks support it, and resampling preserves the total intensity; spectra of several species are best aligned one species at a time
144
144
  - **Peak Detection**: Local maxima and persistent homology methods
145
145
  - **Peak Sets**: `PeakSet` / `PeakList` represent each spectrum as a variable-length `(m/z, intensity)` set for peak-set models, via `MaldiPeakDetector.transform_peaklist()` on binned spectra or `create_peakset_input()` at full m/z resolution; extraction is stateless per spectrum, so results are cacheable without leakage
146
146
  - **Fit-Free Peak Alignment**: `align_peaks()` warps a peak set onto caller-supplied reference peaks, so the reference stays fitted on the training split
@@ -79,7 +79,7 @@ pip install -e .[dev]
79
79
  - **Replicate Merging**: Mean/median/weighted merging with correlation-based outlier detection
80
80
 
81
81
  ### Alignment & Detection
82
- - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra
82
+ - **Spectral Alignment**: Shift, linear, piecewise, DTW, quadratic, cubic, and LOWESS warping for both binned and raw full-resolution spectra. Spectra are first shifted and then aligned to the reference of the shifted spectra, a non-rigid warp is kept only when the matched peaks support it, and resampling preserves the total intensity; spectra of several species are best aligned one species at a time
83
83
  - **Peak Detection**: Local maxima and persistent homology methods
84
84
  - **Peak Sets**: `PeakSet` / `PeakList` represent each spectrum as a variable-length `(m/z, intensity)` set for peak-set models, via `MaldiPeakDetector.transform_peaklist()` on binned spectra or `create_peakset_input()` at full m/z resolution; extraction is stateless per spectrum, so results are cacheable without leakage
85
85
  - **Fit-Free Peak Alignment**: `align_peaks()` warps a peak set onto caller-supplied reference peaks, so the reference stays fitted on the training split
@@ -27,7 +27,7 @@ Examples
27
27
  from .dataset import MaldiSet
28
28
  from .spectrum import MaldiSpectrum
29
29
 
30
- __version__ = "0.20.0"
30
+ __version__ = "0.22.0"
31
31
  __author__ = "Ettore Rocchi"
32
32
 
33
33
  __all__ = [
@@ -25,18 +25,18 @@ def _build_strategy(
25
25
  ) -> AlignmentStrategy:
26
26
  """Instantiate the requested alignment strategy with its parameters."""
27
27
  cls = _ALIGNMENT_REGISTRY[method]
28
+ # Peak sets carry no spectrum to correlate with: estimate from peaks only.
29
+ shared = dict(max_shift=max_shift_da, estimator="peaks", min_gain=None)
28
30
  if method in (AlignmentMethod.shift, AlignmentMethod.linear):
29
- return cls(max_shift=max_shift_da)
31
+ return cls(**shared)
30
32
  if method == AlignmentMethod.piecewise:
31
- return cls(
32
- n_segments=n_segments, smooth_sigma=smooth_sigma, max_shift=max_shift_da
33
- )
33
+ return cls(n_segments=n_segments, smooth_sigma=smooth_sigma, **shared)
34
34
  if method == AlignmentMethod.quadratic:
35
- return cls(max_shift=max_shift_da, degree=2)
35
+ return cls(degree=2, **shared)
36
36
  if method == AlignmentMethod.cubic:
37
- return cls(max_shift=max_shift_da, degree=3)
37
+ return cls(degree=3, **shared)
38
38
  if method == AlignmentMethod.lowess:
39
- return cls(max_shift=max_shift_da, frac=lowess_frac, it=lowess_it)
39
+ return cls(frac=lowess_frac, it=lowess_it, **shared)
40
40
  return cls()
41
41
 
42
42
 
@@ -67,8 +67,9 @@ def align_peaks(
67
67
  Warping strategy. ``"dtw"`` is unsupported for peak sets because it
68
68
  resamples onto a dense grid and would destroy the set structure.
69
69
  max_shift_da : float, default=50.0
70
- Maximum allowed shift in Daltons (used by the shift fallback and the
71
- rigid/linear strategies).
70
+ Largest shift considered, in Daltons. Sample and reference peaks are
71
+ paired only when mutually nearest and within this distance (around
72
+ the global shift, for the non-rigid methods).
72
73
  n_segments : int, default=5
73
74
  Number of segments for ``method="piecewise"``.
74
75
  smooth_sigma : float, default=2.0
@@ -96,6 +97,10 @@ def align_peaks(
96
97
  from the median spacing of the input points; on a sparse peak set that
97
98
  spacing is large, so the smoothing is effectively inactive and the warp
98
99
  reduces to an unsmoothed piecewise-linear transform.
100
+
101
+ The global shift is the median offset of the mutually nearest peak
102
+ pairs. A non-rigid warp whose mapping is not strictly increasing is
103
+ replaced by that global shift, with a warning.
99
104
  """
100
105
  method = AlignmentMethod(method)
101
106
  if method == AlignmentMethod.dtw:
@@ -16,7 +16,15 @@ from ..io.readers import read_spectrum
16
16
  from ..preprocessing.binning import BinningMethod, bin_spectrum
17
17
  from ..preprocessing.pipeline import preprocess
18
18
  from ..preprocessing.preprocessing_pipeline import PreprocessingPipeline
19
- from .strategies import _ALIGNMENT_REGISTRY, AlignmentMethod
19
+ from .strategies import (
20
+ _ALIGNMENT_REGISTRY,
21
+ _SHIFT_ESTIMATORS,
22
+ AlignmentMethod,
23
+ DTWStrategy,
24
+ ShiftStrategy,
25
+ _most_prominent,
26
+ )
27
+ from .warping import _renormalize
20
28
 
21
29
 
22
30
  def create_raw_input(
@@ -76,7 +84,7 @@ def create_raw_input(
76
84
  >>> # Use in pipeline
77
85
  >>> from sklearn.pipeline import Pipeline
78
86
  >>> pipe = Pipeline([
79
- ... ("warp", RawWarping(method="piecewise")),
87
+ ... ("warp", RawWarping(method="lowess")),
80
88
  ... ("scaler", StandardScaler()),
81
89
  ... ])
82
90
  >>> X_binned = pipe.fit_transform(X_raw)
@@ -117,7 +125,7 @@ class RawWarping(BaseEstimator, TransformerMixin):
117
125
  ----------
118
126
  method : str, default="shift"
119
127
  Warping method:
120
- - "shift" : global m/z shift in Daltons
128
+ - "shift" : global m/z shift in Daltons, see ``shift_estimator``
121
129
  - "linear" : linear m/z transformation (mz' = a*mz + b)
122
130
  - "piecewise" : segment-wise m/z shifts with smoothing
123
131
  - "dtw" : dynamic time warping
@@ -130,12 +138,17 @@ class RawWarping(BaseEstimator, TransformerMixin):
130
138
  Binning method. One of 'uniform', 'proportional', 'adaptive', 'custom'.
131
139
  bin_kwargs : dict, optional
132
140
  Additional keyword arguments for binning.
133
- max_shift_da : float, default=50.0
134
- Maximum allowed shift in Daltons.
141
+ max_shift_da : float, default=10.0
142
+ Largest shift considered, in Daltons. It bounds the global shift
143
+ (the total of both steps with ``prealign``) and the distance between
144
+ matched peak pairs.
135
145
  n_segments : int, default=5
136
146
  Number of segments for piecewise warping.
137
147
  dtw_radius : int, default=10
138
- Radius constraint for DTW.
148
+ Radius constraint for DTW. DTW memory grows with the square of the
149
+ number of reference points (about 10 GB per spectrum for a 0.5 Da
150
+ grid over 2-20 kDa), so a coarser ``interp_step`` is needed to run
151
+ it on full-resolution spectra.
139
152
  smooth_sigma : float, default=2.0
140
153
  Gaussian smoothing for piecewise transitions.
141
154
  lowess_frac : float, default=0.3
@@ -159,6 +172,38 @@ class RawWarping(BaseEstimator, TransformerMixin):
159
172
  n_jobs : int, default=1
160
173
  Number of parallel jobs for transform. Use -1 for all available
161
174
  cores, 1 for sequential processing.
175
+ shift_estimator : {"xcorr", "peaks"}, default="xcorr"
176
+ How the global shift is estimated. ``"xcorr"`` resamples the
177
+ spectrum onto a uniform grid at the reference spacing and takes the
178
+ lag (within ``max_shift_da``) that maximises the Pearson correlation
179
+ with the reference. ``"peaks"`` takes the median offset between
180
+ mutually nearest sample and reference peaks within ``max_shift_da``.
181
+ Either shift is applied only if it raises the correlation with the
182
+ reference by ``min_gain``.
183
+ min_gain : float, default=0.05
184
+ Minimum relative gain in correlation with the reference (as a
185
+ fraction of the correlation it improves on) required to apply a
186
+ shift and to keep the "dtw" result instead of the unwarped spectrum.
187
+ With ``prealign``, each step is checked against its own reference.
188
+ min_support : float, default=0.6
189
+ Share of the matched peak pairs that a non-rigid warp must bring
190
+ within ``max_shift_da / 3`` Daltons of their reference partner to be
191
+ used instead of the global shift.
192
+ n_peaks : int or None, default=200
193
+ Number of most prominent detected peaks, in each spectrum and in
194
+ the reference, used to match peaks (by the "peaks" shift estimator
195
+ and the non-rigid methods). ``None`` uses every detected peak.
196
+ prealign : bool, default=True
197
+ Shift the m/z axis of each spectrum first and fit the alignment to
198
+ the median of the shifted training spectra; :meth:`transform`
199
+ applies the same two steps at full resolution before binning, and
200
+ the global shift of both steps together stays within
201
+ ``max_shift_da``. This keeps a median reference sharp when the
202
+ spectra come from different calibrations (for instance several
203
+ instruments).
204
+ renormalize : bool, default=False
205
+ Divide each aligned, binned spectrum by its total intensity, so
206
+ that it sums to 1 (spectra with no intensity are left unchanged).
162
207
 
163
208
  Attributes
164
209
  ----------
@@ -167,7 +212,12 @@ class RawWarping(BaseEstimator, TransformerMixin):
167
212
  ref_intensity_ : np.ndarray
168
213
  Reference spectrum intensities (after fit).
169
214
  ref_peaks_mz_ : np.ndarray
170
- Peak m/z positions in reference spectrum.
215
+ m/z positions of the ``n_peaks`` most prominent peaks of the
216
+ reference spectrum.
217
+ prealign_mz_, prealign_intensity_, prealign_peaks_mz_ : np.ndarray or None
218
+ Reference of the global-shift step applied first (and its peaks),
219
+ with ``prealign=True``; otherwise None. ``ref_mz_`` and
220
+ ``ref_intensity_`` then hold the reference of the shifted spectra.
171
221
  output_columns_ : pd.Index
172
222
  Column names for output DataFrame (m/z bin centers).
173
223
  pipeline_ : PreprocessingPipeline
@@ -184,7 +234,7 @@ class RawWarping(BaseEstimator, TransformerMixin):
184
234
  >>>
185
235
  >>> # Use in sklearn pipeline
186
236
  >>> pipe = Pipeline([
187
- ... ("warp", RawWarping(method="piecewise", bin_width=3)),
237
+ ... ("warp", RawWarping(method="lowess", bin_width=3)),
188
238
  ... ("scaler", StandardScaler()),
189
239
  ... ("clf", RandomForestClassifier())
190
240
  ... ])
@@ -197,6 +247,22 @@ class RawWarping(BaseEstimator, TransformerMixin):
197
247
  - Column "path": paths to raw spectrum files
198
248
 
199
249
  Use `create_raw_input()` to easily create this DataFrame from a directory.
250
+
251
+ The non-rigid methods fit their warp on the ``n_peaks`` most prominent
252
+ peaks, matched around the global shift (after the shift step of
253
+ ``prealign``), and use it only if the warped m/z axis is strictly
254
+ increasing (otherwise the global shift is used, with a warning) and it
255
+ is supported by at least ``min_support`` of the matched pairs. Apart
256
+ from "dtw", every method moves the m/z values and leaves the
257
+ intensities unchanged, so binning preserves the total intensity within
258
+ the m/z range; "dtw" does not, because it averages the sample points
259
+ matched to each reference point. ``renormalize=True`` makes every
260
+ output spectrum sum to 1 whatever the method.
261
+
262
+ Only the reference spectrum is learned in :meth:`fit`; :meth:`transform`
263
+ aligns each spectrum on its own (replicates of one sample are averaged
264
+ after alignment), so its result does not depend on the other spectra
265
+ transformed with it.
200
266
  """
201
267
 
202
268
  def __init__(
@@ -205,7 +271,7 @@ class RawWarping(BaseEstimator, TransformerMixin):
205
271
  bin_width: float = 3,
206
272
  bin_method: str | BinningMethod = BinningMethod.uniform,
207
273
  bin_kwargs: dict | None = None,
208
- max_shift_da: float = 50.0,
274
+ max_shift_da: float = 10.0,
209
275
  n_segments: int = 5,
210
276
  dtw_radius: int = 10,
211
277
  smooth_sigma: float = 2.0,
@@ -217,6 +283,12 @@ class RawWarping(BaseEstimator, TransformerMixin):
217
283
  min_reference_peaks: int = 5,
218
284
  interp_step: float = 0.5,
219
285
  n_jobs: int = 1,
286
+ shift_estimator: str = "xcorr",
287
+ min_gain: float = 0.05,
288
+ min_support: float = 0.6,
289
+ n_peaks: int | None = 200,
290
+ prealign: bool = True,
291
+ renormalize: bool = False,
220
292
  ) -> None:
221
293
  self.method = AlignmentMethod(method)
222
294
  self.bin_width = bin_width
@@ -236,6 +308,12 @@ class RawWarping(BaseEstimator, TransformerMixin):
236
308
  )
237
309
  self.min_reference_peaks = min_reference_peaks
238
310
  self.n_jobs = n_jobs
311
+ self.shift_estimator = shift_estimator
312
+ self.min_gain = min_gain
313
+ self.min_support = min_support
314
+ self.n_peaks = n_peaks
315
+ self.prealign = prealign
316
+ self.renormalize = renormalize
239
317
 
240
318
  def _load_raw_spectrum(self, path: str) -> pd.DataFrame:
241
319
  """Load and preprocess a raw spectrum from file."""
@@ -249,13 +327,12 @@ class RawWarping(BaseEstimator, TransformerMixin):
249
327
  return preprocess(raw, self.pipeline_)
250
328
 
251
329
  def _detect_peaks_mz(self, mz: np.ndarray, intensity: np.ndarray) -> np.ndarray:
252
- """Detect peaks and return their m/z positions using the peak detector."""
330
+ """m/z positions of the ``n_peaks`` most prominent detected peaks."""
253
331
  # Create temporary single-row DataFrame for peak detection
254
332
  spec_df = pd.DataFrame([intensity], columns=mz)
255
333
  peaks_df = self.peak_detector.transform(spec_df)
256
- # Get m/z positions where peaks were detected (non-zero values)
257
- peak_mask = peaks_df.iloc[0].to_numpy() != 0
258
- return mz[peak_mask]
334
+ peaks = np.flatnonzero(peaks_df.iloc[0].to_numpy() != 0)
335
+ return mz[_most_prominent(intensity, peaks, self.n_peaks)]
259
336
 
260
337
  def _compute_raw_reference(
261
338
  self, paths: list[str]
@@ -363,6 +440,18 @@ class RawWarping(BaseEstimator, TransformerMixin):
363
440
  "Use create_raw_input() to create the input DataFrame."
364
441
  )
365
442
 
443
+ if self.shift_estimator not in _SHIFT_ESTIMATORS:
444
+ raise ValueError(
445
+ f"shift_estimator must be one of {_SHIFT_ESTIMATORS}, "
446
+ f"got {self.shift_estimator!r}"
447
+ )
448
+ if self.min_gain < 0:
449
+ raise ValueError(f"min_gain must be >= 0, got {self.min_gain}")
450
+ if not (0.0 <= self.min_support <= 1.0):
451
+ raise ValueError(f"min_support must be in [0, 1], got {self.min_support}")
452
+ if self.n_peaks is not None and self.n_peaks < 1:
453
+ raise ValueError(f"n_peaks must be >= 1 or None, got {self.n_peaks}")
454
+
366
455
  # Store preprocessing config
367
456
  self.pipeline_ = self.pipeline or PreprocessingPipeline.default()
368
457
 
@@ -373,6 +462,16 @@ class RawWarping(BaseEstimator, TransformerMixin):
373
462
  self.ref_mz_, self.ref_intensity_, self.ref_peaks_mz_ = (
374
463
  self._compute_raw_reference(paths)
375
464
  )
465
+ self.prealign_mz_ = self.prealign_intensity_ = self.prealign_peaks_mz_ = None
466
+ if self._prealigns():
467
+ self.prealign_mz_ = self.ref_mz_
468
+ self.prealign_intensity_ = self.ref_intensity_
469
+ self.prealign_peaks_mz_ = self.ref_peaks_mz_
470
+ if self.reference == "median":
471
+ self.ref_intensity_ = self._shifted_median(paths)
472
+ self.ref_peaks_mz_ = self._detect_peaks_mz(
473
+ self.ref_mz_, self.ref_intensity_
474
+ )
376
475
 
377
476
  # Validate reference quality
378
477
  n_peaks = len(self.ref_peaks_mz_)
@@ -394,30 +493,64 @@ class RawWarping(BaseEstimator, TransformerMixin):
394
493
  def _get_strategy(self):
395
494
  """Build strategy instance from current parameters."""
396
495
  cls = _ALIGNMENT_REGISTRY[self.method]
496
+ shared = dict(
497
+ max_shift=self.max_shift_da,
498
+ estimator=self.shift_estimator,
499
+ min_gain=self.min_gain,
500
+ )
501
+ warp = dict(shared, min_support=self.min_support)
397
502
  if self.method == "shift":
398
- return cls(max_shift=self.max_shift_da)
503
+ return cls(**shared)
399
504
  elif self.method == "linear":
400
- return cls(max_shift=self.max_shift_da)
505
+ return cls(**warp)
401
506
  elif self.method == "piecewise":
402
507
  return cls(
403
- n_segments=self.n_segments,
404
- smooth_sigma=self.smooth_sigma,
405
- max_shift=self.max_shift_da,
508
+ n_segments=self.n_segments, smooth_sigma=self.smooth_sigma, **warp
406
509
  )
407
510
  elif self.method == "dtw":
408
- return cls(dtw_radius=self.dtw_radius)
511
+ return cls(dtw_radius=self.dtw_radius, min_gain=self.min_gain)
409
512
  elif self.method == "quadratic":
410
- return cls(max_shift=self.max_shift_da, degree=2)
513
+ return cls(degree=2, **warp)
411
514
  elif self.method == "cubic":
412
- return cls(max_shift=self.max_shift_da, degree=3)
515
+ return cls(degree=3, **warp)
413
516
  elif self.method == "lowess":
414
- return cls(
415
- max_shift=self.max_shift_da,
416
- frac=self.lowess_frac,
417
- it=self.lowess_it,
418
- )
517
+ return cls(frac=self.lowess_frac, it=self.lowess_it, **warp)
419
518
  return cls()
420
519
 
520
+ def _prealigns(self) -> bool:
521
+ """Whether a global-shift step runs before the alignment."""
522
+ return bool(self.prealign)
523
+
524
+ def _prealign_shift(
525
+ self,
526
+ mz: np.ndarray,
527
+ intensity: np.ndarray,
528
+ peaks_mz: np.ndarray | None = None,
529
+ ) -> float:
530
+ """Global shift (in Daltons) of a spectrum to the prealign reference."""
531
+ if peaks_mz is None and self.shift_estimator == "peaks":
532
+ peaks_mz = self._detect_peaks_mz(mz, intensity)
533
+ strategy = ShiftStrategy(self.max_shift_da, self.shift_estimator, self.min_gain)
534
+ return strategy.estimate_raw(
535
+ mz,
536
+ intensity,
537
+ peaks_mz,
538
+ self.prealign_peaks_mz_,
539
+ self.prealign_mz_,
540
+ self.prealign_intensity_,
541
+ )
542
+
543
+ def _shifted_median(self, paths: list[str]) -> np.ndarray:
544
+ """Median on ``ref_mz_`` of the training spectra after their shift."""
545
+ rows = np.empty((len(paths), len(self.ref_mz_)))
546
+ for i, path in enumerate(paths):
547
+ spec_df = self._load_raw_spectrum(path)
548
+ mz = spec_df["mass"].to_numpy()
549
+ intensity = spec_df["intensity"].to_numpy()
550
+ shift = self._prealign_shift(mz, intensity)
551
+ rows[i] = np.interp(self.ref_mz_, mz + shift, intensity)
552
+ return np.median(rows, axis=0)
553
+
421
554
  def _bin_warped(self, mz: np.ndarray, intensity: np.ndarray) -> pd.DataFrame:
422
555
  """Bin warped raw spectrum to output grid."""
423
556
  warped_df = pd.DataFrame({"mass": mz, "intensity": intensity})
@@ -440,8 +573,18 @@ class RawWarping(BaseEstimator, TransformerMixin):
440
573
  intensity = spec_df["intensity"].to_numpy()
441
574
 
442
575
  peaks_mz = self._detect_peaks_mz(mz, intensity)
576
+ applied = 0.0
577
+ if self.prealign_mz_ is not None:
578
+ applied = self._prealign_shift(mz, intensity, peaks_mz)
579
+ mz, peaks_mz = mz + applied, peaks_mz + applied
443
580
 
444
581
  strategy = self._get_strategy()
582
+ budget = {}
583
+ if not isinstance(strategy, DTWStrategy):
584
+ budget["shift_bounds"] = (
585
+ -self.max_shift_da - applied,
586
+ self.max_shift_da - applied,
587
+ )
445
588
  warped_mz, warped_int = strategy.align_raw(
446
589
  mz,
447
590
  intensity,
@@ -449,6 +592,7 @@ class RawWarping(BaseEstimator, TransformerMixin):
449
592
  self.ref_peaks_mz_,
450
593
  self.ref_mz_,
451
594
  self.ref_intensity_,
595
+ **budget,
452
596
  )
453
597
 
454
598
  binned = self._bin_warped(warped_mz, warped_int)
@@ -495,9 +639,10 @@ class RawWarping(BaseEstimator, TransformerMixin):
495
639
  )
496
640
 
497
641
  # Combine into DataFrame
498
- result = pd.DataFrame(
499
- np.vstack(aligned_rows), index=X.index, columns=self.output_columns_
500
- )
642
+ aligned = np.vstack(aligned_rows)
643
+ if self.renormalize:
644
+ _renormalize(aligned)
645
+ result = pd.DataFrame(aligned, index=X.index, columns=self.output_columns_)
501
646
 
502
647
  # Average replicates when create_raw_input used strategy="average"
503
648
  if "_original_id" in X.columns: