augmentrum 0.0.2__tar.gz → 0.0.3__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 (96) hide show
  1. {augmentrum-0.0.2 → augmentrum-0.0.3}/MANIFEST.in +0 -1
  2. {augmentrum-0.0.2/augmentrum.egg-info → augmentrum-0.0.3}/PKG-INFO +97 -17
  3. {augmentrum-0.0.2 → augmentrum-0.0.3}/README.md +71 -13
  4. augmentrum-0.0.3/augmentrum/__init__.py +41 -0
  5. augmentrum-0.0.3/augmentrum/augmentation/__init__.py +56 -0
  6. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/augmentation/amplitude_scaling.py +29 -17
  7. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/augmentation/apodization.py +108 -54
  8. augmentrum-0.0.3/augmentrum/augmentation/artificial_peaks.py +344 -0
  9. augmentrum-0.0.3/augmentrum/augmentation/baseline_augmentation.py +704 -0
  10. augmentrum-0.0.3/augmentrum/augmentation/eddy_current.py +486 -0
  11. augmentrum-0.0.3/augmentrum/augmentation/line_broadening.py +485 -0
  12. augmentrum-0.0.3/augmentrum/augmentation/macromolecules.py +758 -0
  13. augmentrum-0.0.3/augmentrum/augmentation/noise.py +1003 -0
  14. augmentrum-0.0.3/augmentrum/augmentation/phase_frequency.py +385 -0
  15. augmentrum-0.0.3/augmentrum/augmentation/residual_water.py +377 -0
  16. augmentrum-0.0.3/augmentrum/augmentation/spatial_augmentations.py +1019 -0
  17. augmentrum-0.0.3/augmentrum/augmentation/spurious_echoes.py +709 -0
  18. augmentrum-0.0.3/augmentrum/augmentation/transient_synthesis.py +303 -0
  19. augmentrum-0.0.3/augmentrum/augmentation/updating.py +590 -0
  20. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/augmentation/zero_fill.py +33 -16
  21. augmentrum-0.0.3/augmentrum/core/__init__.py +23 -0
  22. augmentrum-0.0.3/augmentrum/core/augmentrum.py +1932 -0
  23. augmentrum-0.0.3/augmentrum/core/base_module.py +833 -0
  24. augmentrum-0.0.3/augmentrum/core/dataset_utils.py +528 -0
  25. augmentrum-0.0.3/augmentrum/core/kernels.py +2346 -0
  26. augmentrum-0.0.3/augmentrum/core/pipeline.py +753 -0
  27. augmentrum-0.0.3/augmentrum/core/pool.py +611 -0
  28. augmentrum-0.0.3/augmentrum/core/precision.py +109 -0
  29. augmentrum-0.0.3/augmentrum/dataset/biggaba.py +286 -0
  30. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/dataset/brainbeats.py +6 -14
  31. augmentrum-0.0.3/augmentrum/dataset/cows.py +792 -0
  32. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/dataset/fmrsinpain.py +4 -12
  33. augmentrum-0.0.3/augmentrum/dataset/mrsi_challenge.py +874 -0
  34. augmentrum-0.0.3/augmentrum/physics/__init__.py +19 -0
  35. augmentrum-0.0.3/augmentrum/physics/seq_file.py +137 -0
  36. augmentrum-0.0.3/augmentrum/processing/__init__.py +51 -0
  37. augmentrum-0.0.3/augmentrum/processing/domain.py +215 -0
  38. augmentrum-0.0.3/augmentrum/processing/edit_combination.py +126 -0
  39. augmentrum-0.0.3/augmentrum/processing/interpolating.py +733 -0
  40. augmentrum-0.0.3/augmentrum/processing/raw_processing.py +2196 -0
  41. augmentrum-0.0.3/augmentrum/processing/torch_engine.py +1383 -0
  42. augmentrum-0.0.3/augmentrum/processing/utils.py +816 -0
  43. augmentrum-0.0.3/augmentrum/sampling/__init__.py +76 -0
  44. augmentrum-0.0.3/augmentrum/sampling/coil_sampling.py +898 -0
  45. augmentrum-0.0.3/augmentrum/sampling/dimension_sampling.py +495 -0
  46. augmentrum-0.0.3/augmentrum/sampling/kspace_reconstructor.py +713 -0
  47. augmentrum-0.0.3/augmentrum/sampling/kspace_sampling.py +4415 -0
  48. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/sampling/subject_splitter.py +113 -15
  49. augmentrum-0.0.3/augmentrum/utils/__init__.py +29 -0
  50. augmentrum-0.0.3/augmentrum/utils/download.py +178 -0
  51. augmentrum-0.0.3/augmentrum/utils/geometry.py +138 -0
  52. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/utils/plotting.py +238 -19
  53. {augmentrum-0.0.2 → augmentrum-0.0.3/augmentrum.egg-info}/PKG-INFO +97 -17
  54. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum.egg-info/SOURCES.txt +26 -11
  55. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum.egg-info/requires.txt +32 -3
  56. {augmentrum-0.0.2 → augmentrum-0.0.3}/pyproject.toml +40 -10
  57. augmentrum-0.0.3/tests/test_module_precision.py +72 -0
  58. augmentrum-0.0.3/tests/test_module_registry.py +262 -0
  59. {augmentrum-0.0.2 → augmentrum-0.0.3}/tests/test_readme_table.py +131 -147
  60. augmentrum-0.0.3/tests/test_reproducibility.py +141 -0
  61. augmentrum-0.0.2/assets/figures/logo.png +0 -0
  62. augmentrum-0.0.2/augmentrum/__init__.py +0 -23
  63. augmentrum-0.0.2/augmentrum/augmentation/__init__.py +0 -32
  64. augmentrum-0.0.2/augmentrum/augmentation/artificial_peaks.py +0 -334
  65. augmentrum-0.0.2/augmentrum/augmentation/baseline_augmentation.py +0 -544
  66. augmentrum-0.0.2/augmentrum/augmentation/eddy_current.py +0 -278
  67. augmentrum-0.0.2/augmentrum/augmentation/gaussian_noise.py +0 -211
  68. augmentrum-0.0.2/augmentrum/augmentation/line_broadening.py +0 -179
  69. augmentrum-0.0.2/augmentrum/augmentation/phase_frequency.py +0 -259
  70. augmentrum-0.0.2/augmentrum/augmentation/residual_water.py +0 -264
  71. augmentrum-0.0.2/augmentrum/augmentation/spatial_augmentations.py +0 -859
  72. augmentrum-0.0.2/augmentrum/augmentation/spurious_echoes.py +0 -378
  73. augmentrum-0.0.2/augmentrum/core/__init__.py +0 -10
  74. augmentrum-0.0.2/augmentrum/core/augmentrum.py +0 -1172
  75. augmentrum-0.0.2/augmentrum/core/base_module.py +0 -423
  76. augmentrum-0.0.2/augmentrum/core/dataset_utils.py +0 -417
  77. augmentrum-0.0.2/augmentrum/core/nifti_mrs_plus.py +0 -1079
  78. augmentrum-0.0.2/augmentrum/core/pipeline.py +0 -358
  79. augmentrum-0.0.2/augmentrum/dataset/cows.py +0 -302
  80. augmentrum-0.0.2/augmentrum/processing/bicubic_interpolator.py +0 -296
  81. augmentrum-0.0.2/augmentrum/processing/nifti_raw_processor.py +0 -389
  82. augmentrum-0.0.2/augmentrum/processing/tricubic_interpolator.py +0 -293
  83. augmentrum-0.0.2/augmentrum/processing/utils.py +0 -414
  84. augmentrum-0.0.2/augmentrum/resources/__init__.py +0 -14
  85. augmentrum-0.0.2/augmentrum/resources/scans/__init__.py +0 -0
  86. augmentrum-0.0.2/augmentrum/sampling/coil_average_sampler.py +0 -274
  87. augmentrum-0.0.2/augmentrum/sampling/kspace_reconstructor.py +0 -275
  88. augmentrum-0.0.2/augmentrum/sampling/kspace_sampling.py +0 -2562
  89. augmentrum-0.0.2/augmentrum/utils/__init__.py +0 -10
  90. augmentrum-0.0.2/augmentrum/utils/tensor_ops.py +0 -203
  91. {augmentrum-0.0.2 → augmentrum-0.0.3}/LICENSE +0 -0
  92. {augmentrum-0.0.2 → augmentrum-0.0.3}/assets/figures/overview.png +0 -0
  93. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum/utils/philips.py +0 -0
  94. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum.egg-info/dependency_links.txt +0 -0
  95. {augmentrum-0.0.2 → augmentrum-0.0.3}/augmentrum.egg-info/top_level.txt +0 -0
  96. {augmentrum-0.0.2 → augmentrum-0.0.3}/setup.cfg +0 -0
@@ -1,7 +1,6 @@
1
1
  include README.md
2
2
  include LICENSE
3
3
  recursive-include augmentrum *.py
4
- recursive-include augmentrum/resources/scans *
5
4
  recursive-include assets/figures *.png *.jpg *.svg
6
5
  recursive-exclude * __pycache__
7
6
  recursive-exclude * *.pyc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: augmentrum
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A Data Augmentation Package for MR Spectroscopy
5
5
  Author-email: "John T. LaMaster" <jlamaste@gmail.com>, "Julian P. Merkofer" <j.p.merkofer@tue.nl>, "Kay C. Igwe" <kci2104@columbia.edu>
6
6
  License: Apache-2.0
@@ -23,14 +23,14 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: numpy>=1.21.0
25
25
  Requires-Dist: scipy>=1.7.0
26
- Requires-Dist: torch>=1.10.0
27
- Requires-Dist: fsl-mrs>=2.1.0
28
26
  Requires-Dist: nifti-mrs>=0.1.0
27
+ Requires-Dist: nifti-mrs-plus[ops]>=0.2.1
29
28
  Requires-Dist: spec2nii>=0.6.0
30
- Requires-Dist: torchkbnufft>=1.3.0
31
29
  Requires-Dist: tqdm>=4.62.0
32
30
  Requires-Dist: matplotlib>=3.4.0
33
31
  Requires-Dist: shutup>=0.2.0
32
+ Provides-Extra: fsl
33
+ Requires-Dist: fsl-mrs>=2.1.0; extra == "fsl"
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: pytest>=7.0.0; extra == "dev"
36
36
  Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
@@ -38,20 +38,42 @@ Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
38
38
  Requires-Dist: black>=22.0.0; extra == "dev"
39
39
  Requires-Dist: flake8>=4.0.0; extra == "dev"
40
40
  Requires-Dist: mypy>=0.950; extra == "dev"
41
+ Provides-Extra: torch
42
+ Requires-Dist: torch>=1.10.0; extra == "torch"
41
43
  Provides-Extra: jax
42
44
  Requires-Dist: jax>=0.4.0; extra == "jax"
43
45
  Requires-Dist: jaxlib>=0.4.0; extra == "jax"
46
+ Provides-Extra: nufft
47
+ Requires-Dist: torch>=1.10.0; extra == "nufft"
48
+ Requires-Dist: torchkbnufft>=1.3.0; extra == "nufft"
49
+ Provides-Extra: tensorflow
50
+ Requires-Dist: tensorflow>=2.8.0; extra == "tensorflow"
51
+ Provides-Extra: csm
52
+ Requires-Dist: h5py>=3.0.0; extra == "csm"
44
53
  Provides-Extra: keras
45
54
  Requires-Dist: keras>=3.0.0; extra == "keras"
55
+ Provides-Extra: seq
56
+ Requires-Dist: pypulseq>=1.4.0; extra == "seq"
57
+ Provides-Extra: docs
58
+ Requires-Dist: sphinx; extra == "docs"
59
+ Requires-Dist: sphinx-autobuild; extra == "docs"
60
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
61
+ Requires-Dist: sphinx-copybutton; extra == "docs"
62
+ Requires-Dist: myst-parser; extra == "docs"
63
+ Requires-Dist: furo; extra == "docs"
46
64
  Provides-Extra: all
65
+ Requires-Dist: torch>=1.10.0; extra == "all"
66
+ Requires-Dist: torchkbnufft>=1.3.0; extra == "all"
67
+ Requires-Dist: h5py>=3.0.0; extra == "all"
47
68
  Requires-Dist: tensorflow>=2.8.0; extra == "all"
48
69
  Requires-Dist: keras>=3.0.0; extra == "all"
49
70
  Requires-Dist: jax>=0.4.0; extra == "all"
50
71
  Requires-Dist: jaxlib>=0.4.0; extra == "all"
72
+ Requires-Dist: pypulseq>=1.4.0; extra == "all"
51
73
  Dynamic: license-file
52
74
 
53
75
  <div align="center">
54
- <img src="https://raw.githubusercontent.com/julianmer/Augmentrum/main/assets/figures/logo.png" alt="Augmentrum Logo" width="100" style="margin-bottom: -10px;"/>
76
+ <img src="https://raw.githubusercontent.com/julianmer/Augmentrum/main/assets/logo/logo.png" alt="Augmentrum Logo" width="100" style="margin-bottom: -10px;"/>
55
77
  <h1 style="margin-top: 5px; margin-bottom: 5px;">Augmentrum</h1>
56
78
  <p style="margin-top: 0px;"><em>A Data Augmentation Package for MR Spectroscopy</em></p>
57
79
 
@@ -131,6 +153,58 @@ augmenter = Augmentrum(
131
153
  train_data = augmenter.dataloader()
132
154
  ```
133
155
 
156
+ ### Supervised Pairs: Taps and `outputs`
157
+
158
+ Any pipeline stage can be named with a `'tap:<name>'` marker and yielded by the
159
+ dataloaders through the `outputs` spec — an arbitrarily nested tuple of stage
160
+ tokens (`'data'` / `'water'` for the pipeline end, `'<tap>'` / `'<tap>.water'`
161
+ for a tapped stage). The classic `(data, water)` pair stays the default.
162
+
163
+ ```python
164
+ augmenter = Augmentrum(
165
+ data=data,
166
+ pipeline=['coil_sampling', 'macromolecules', 'tap:clean', 'undersampling'],
167
+ outputs=(('data', 'water'), ('clean', 'clean.water')),
168
+ backend='pytorch',
169
+ )
170
+
171
+ # supervised reconstruction pairs: input fully augmented, target frozen at the tap
172
+ for (x, x_water), (y, y_water) in augmenter.dataloader():
173
+ train_step(x, y)
174
+ ```
175
+
176
+ ### Per-Step Parameters, Seeds and Subject-Wise Splits
177
+
178
+ A keyword argument reaches every module whose constructor names it, so
179
+ `lb_hz=(0, 5)` would move `LineBroadening` *and* `Apodization` (you get a
180
+ warning). A pipeline entry can carry its own kwargs, which reach that step only
181
+ and override the globals there; a kwarg no module accepts raises with the
182
+ closest valid names instead of being ignored.
183
+
184
+ ```python
185
+ augmenter = Augmentrum(
186
+ data=data,
187
+ groups=subject_ids, # one id per item: a subject never straddles splits
188
+ split_fractions={'val': 0.1, 'test': 0.1},
189
+ pipeline=[
190
+ 'coil_sampling',
191
+ {'noise': {'sigma_frac': (0.0, 0.02)}},
192
+ {'line_broadening': {'lb_hz': (0.0, 5.0)}},
193
+ ('apodization', {'mode': 'exponential', 'lb_hz': 2.0}),
194
+ 'baseline_bspline', # registry alias fixing mode='bspline'
195
+ ],
196
+ n_coils=(1, 8), # integer-typed ranges are inclusive
197
+ seed=1, # fixes the split, subject draws, ranges and modules
198
+ )
199
+ augmenter.split_groups['val'] # the subject ids held out for validation
200
+ ```
201
+
202
+ Without `groups`, a user-defined `SubjectID` header-extension field is used
203
+ when every item carries one. `seed` makes the whole run replay — including
204
+ `num_workers > 0` DataLoaders, where each worker is reseeded from it — and
205
+ `augmenter.reseed(k)` restarts every stream; `'fixed'` mode draws its ranges
206
+ once per split and keeps them across `dataloader()` calls.
207
+
134
208
  ---
135
209
 
136
210
  ## Module Reference & Backend Support
@@ -143,30 +217,36 @@ no manual conversion needed.
143
217
  | Module | Modes / Methods | NIfTI | NumPy | PyTorch | TensorFlow | JAX | Keras |
144
218
  |:---|:--------------------------------------------------------|:-----:|:---:|:---:|:---:|:---:|:---:|
145
219
  | `AmplitudeScaling` | uniform, normal | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
146
- | `Apodization` | exponential, truncate | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
220
+ | `Apodization` | exponential, gaussian, hamming, truncate | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
147
221
  | `ArtificialPeaks` | Lorentzian, Gaussian, Voigt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
222
+ | `AverageSampler` | random, consecutive, strided, deterministic | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
148
223
  | `BaselineAugmentation` | random_walk, bspline, polynomial | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
149
- | `CoilAverageSampler` | random, deterministic | ✓ | ~ | ~ | ~ | ~ | ~ |
150
- | `EddyCurrent` | synthetic, water | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
151
- | `FrequencyShift` | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
152
- | `GaussianNoise` | sigma, sigma_frac, snr, snr_db | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
153
- | `KspaceReconstructor` | NUFFT + density compensation | — | — | ✓ | — | — | — |
224
+ | `CoilSampler` | draw random, reweight, grow from CSMs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
225
+ | `EddyCurrent` | synthetic, water (call-time reference or source library) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
226
+ | `EditCombiner` | diff, sum along DIM_EDIT | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
227
+ | `FrequencyShift` | shift_hz | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
228
+ | `Noise` | sigma, sigma_frac, snr, snr_db | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
229
+ | `KspaceUndersampling` | cartesian, gridded, nufft, dual trajectory | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
154
230
  | `LineBroadening` | lorentzian, gaussian, voigt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
155
- | `NIfTI_RawProcessor` | coil combination, alignment, averaging, ECC, phase/freq | ✓ | ~ | ~ | ~ | ~ | ~ |
231
+ | `Macromolecules` | parametrized, measured, supplied | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
232
+ | `RawProcessor` | coil combination, registration, ECC, ... | ✓ | ✓‡ | ✓‡ | ✓‡ | ✓‡ | ✓‡ |
156
233
  | `PhaseShift` | zero_order, first_order | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
157
- | `ResidualWater` | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
158
- | `SpatialAugmentations` | 2-D / 3-D affine, flip, zoom, shear | ✓ ‡ | ✓ ‡ | ✓ | ~ | ~ | ~ |
159
- | `SpuriousEchoes` | replica, hybrid | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
234
+ | `ResidualWater` | lobes, turco (7-Lorentzian WaterFit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
235
+ | `SpatialAugmentations` | 2-D / 3-D affine, flip, zoom, shear | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
236
+ | `SpuriousEchoes` | echo, replica, hybrid | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
237
+ | `Tap` | marker: snapshot a stage for `outputs` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
238
+ | `TransientSynthesizer` | correlated drift, respiration, motion events | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
160
239
  | `ZeroFill` | pad FID to target length | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
161
240
 
162
241
  **✓** native — data tensor stays in the target framework throughout.
163
242
  **~** automatic — the base class routes the call through the NIfTI-list path; the result is returned in the original backend format. Functional, but not natively differentiable.
164
243
  **—** not supported / not applicable.
165
244
 
166
- > **†** `Apodization[truncate]` and `ZeroFill` both change `N_PTS`. Because `target_pts` / `n_pts` is a module-level scalar, the **same length is applied uniformly to every batch member** — the output is still a uniform tensor. On non-NIfTI backends the base class detects the shape change, rebuilds each NIfTI_MRS object at the new length, and emits a `RuntimeWarning`.
245
+ Modes that differ in backend support get a row each, named `Module[mode]`.
167
246
 
168
- > **‡** `SpatialAugmentations` uses `F.grid_sample` (PyTorch) internally; NIfTI-list and NumPy inputs are converted to PyTorch for processing and converted back. TensorFlow, JAX, and Keras are handled by the base-class automatic routing (~).
247
+ > **†** `Apodization[truncate]` and `ZeroFill` both change `N_PTS`. Because `target_pts` / `n_pts` is a module-level scalar, the **same length is applied uniformly to every batch member** — the output is still a uniform tensor.
169
248
 
249
+ > **‡** `RawProcessor[remove_outliers]` on tensor backends **zeroes outlier transients instead of dropping them** — a batched tensor cannot go ragged.
170
250
 
171
251
  ---
172
252
 
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/julianmer/Augmentrum/main/assets/figures/logo.png" alt="Augmentrum Logo" width="100" style="margin-bottom: -10px;"/>
2
+ <img src="https://raw.githubusercontent.com/julianmer/Augmentrum/main/assets/logo/logo.png" alt="Augmentrum Logo" width="100" style="margin-bottom: -10px;"/>
3
3
  <h1 style="margin-top: 5px; margin-bottom: 5px;">Augmentrum</h1>
4
4
  <p style="margin-top: 0px;"><em>A Data Augmentation Package for MR Spectroscopy</em></p>
5
5
 
@@ -79,6 +79,58 @@ augmenter = Augmentrum(
79
79
  train_data = augmenter.dataloader()
80
80
  ```
81
81
 
82
+ ### Supervised Pairs: Taps and `outputs`
83
+
84
+ Any pipeline stage can be named with a `'tap:<name>'` marker and yielded by the
85
+ dataloaders through the `outputs` spec — an arbitrarily nested tuple of stage
86
+ tokens (`'data'` / `'water'` for the pipeline end, `'<tap>'` / `'<tap>.water'`
87
+ for a tapped stage). The classic `(data, water)` pair stays the default.
88
+
89
+ ```python
90
+ augmenter = Augmentrum(
91
+ data=data,
92
+ pipeline=['coil_sampling', 'macromolecules', 'tap:clean', 'undersampling'],
93
+ outputs=(('data', 'water'), ('clean', 'clean.water')),
94
+ backend='pytorch',
95
+ )
96
+
97
+ # supervised reconstruction pairs: input fully augmented, target frozen at the tap
98
+ for (x, x_water), (y, y_water) in augmenter.dataloader():
99
+ train_step(x, y)
100
+ ```
101
+
102
+ ### Per-Step Parameters, Seeds and Subject-Wise Splits
103
+
104
+ A keyword argument reaches every module whose constructor names it, so
105
+ `lb_hz=(0, 5)` would move `LineBroadening` *and* `Apodization` (you get a
106
+ warning). A pipeline entry can carry its own kwargs, which reach that step only
107
+ and override the globals there; a kwarg no module accepts raises with the
108
+ closest valid names instead of being ignored.
109
+
110
+ ```python
111
+ augmenter = Augmentrum(
112
+ data=data,
113
+ groups=subject_ids, # one id per item: a subject never straddles splits
114
+ split_fractions={'val': 0.1, 'test': 0.1},
115
+ pipeline=[
116
+ 'coil_sampling',
117
+ {'noise': {'sigma_frac': (0.0, 0.02)}},
118
+ {'line_broadening': {'lb_hz': (0.0, 5.0)}},
119
+ ('apodization', {'mode': 'exponential', 'lb_hz': 2.0}),
120
+ 'baseline_bspline', # registry alias fixing mode='bspline'
121
+ ],
122
+ n_coils=(1, 8), # integer-typed ranges are inclusive
123
+ seed=1, # fixes the split, subject draws, ranges and modules
124
+ )
125
+ augmenter.split_groups['val'] # the subject ids held out for validation
126
+ ```
127
+
128
+ Without `groups`, a user-defined `SubjectID` header-extension field is used
129
+ when every item carries one. `seed` makes the whole run replay — including
130
+ `num_workers > 0` DataLoaders, where each worker is reseeded from it — and
131
+ `augmenter.reseed(k)` restarts every stream; `'fixed'` mode draws its ranges
132
+ once per split and keeps them across `dataloader()` calls.
133
+
82
134
  ---
83
135
 
84
136
  ## Module Reference & Backend Support
@@ -91,30 +143,36 @@ no manual conversion needed.
91
143
  | Module | Modes / Methods | NIfTI | NumPy | PyTorch | TensorFlow | JAX | Keras |
92
144
  |:---|:--------------------------------------------------------|:-----:|:---:|:---:|:---:|:---:|:---:|
93
145
  | `AmplitudeScaling` | uniform, normal | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
94
- | `Apodization` | exponential, truncate | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
146
+ | `Apodization` | exponential, gaussian, hamming, truncate | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
95
147
  | `ArtificialPeaks` | Lorentzian, Gaussian, Voigt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
148
+ | `AverageSampler` | random, consecutive, strided, deterministic | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
96
149
  | `BaselineAugmentation` | random_walk, bspline, polynomial | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
97
- | `CoilAverageSampler` | random, deterministic | ✓ | ~ | ~ | ~ | ~ | ~ |
98
- | `EddyCurrent` | synthetic, water | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
99
- | `FrequencyShift` | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
100
- | `GaussianNoise` | sigma, sigma_frac, snr, snr_db | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
101
- | `KspaceReconstructor` | NUFFT + density compensation | — | — | ✓ | — | — | — |
150
+ | `CoilSampler` | draw random, reweight, grow from CSMs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
151
+ | `EddyCurrent` | synthetic, water (call-time reference or source library) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
152
+ | `EditCombiner` | diff, sum along DIM_EDIT | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
153
+ | `FrequencyShift` | shift_hz | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
154
+ | `Noise` | sigma, sigma_frac, snr, snr_db | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
155
+ | `KspaceUndersampling` | cartesian, gridded, nufft, dual trajectory | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
102
156
  | `LineBroadening` | lorentzian, gaussian, voigt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
103
- | `NIfTI_RawProcessor` | coil combination, alignment, averaging, ECC, phase/freq | ✓ | ~ | ~ | ~ | ~ | ~ |
157
+ | `Macromolecules` | parametrized, measured, supplied | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
158
+ | `RawProcessor` | coil combination, registration, ECC, ... | ✓ | ✓‡ | ✓‡ | ✓‡ | ✓‡ | ✓‡ |
104
159
  | `PhaseShift` | zero_order, first_order | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
105
- | `ResidualWater` | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
106
- | `SpatialAugmentations` | 2-D / 3-D affine, flip, zoom, shear | ✓ ‡ | ✓ ‡ | ✓ | ~ | ~ | ~ |
107
- | `SpuriousEchoes` | replica, hybrid | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
160
+ | `ResidualWater` | lobes, turco (7-Lorentzian WaterFit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
161
+ | `SpatialAugmentations` | 2-D / 3-D affine, flip, zoom, shear | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
162
+ | `SpuriousEchoes` | echo, replica, hybrid | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
163
+ | `Tap` | marker: snapshot a stage for `outputs` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
164
+ | `TransientSynthesizer` | correlated drift, respiration, motion events | ~ | ✓ | ✓ | ✓ | ✓ | ✓ |
108
165
  | `ZeroFill` | pad FID to target length | ✓ | ✓† | ✓† | ✓† | ✓† | ✓† |
109
166
 
110
167
  **✓** native — data tensor stays in the target framework throughout.
111
168
  **~** automatic — the base class routes the call through the NIfTI-list path; the result is returned in the original backend format. Functional, but not natively differentiable.
112
169
  **—** not supported / not applicable.
113
170
 
114
- > **†** `Apodization[truncate]` and `ZeroFill` both change `N_PTS`. Because `target_pts` / `n_pts` is a module-level scalar, the **same length is applied uniformly to every batch member** — the output is still a uniform tensor. On non-NIfTI backends the base class detects the shape change, rebuilds each NIfTI_MRS object at the new length, and emits a `RuntimeWarning`.
171
+ Modes that differ in backend support get a row each, named `Module[mode]`.
115
172
 
116
- > **‡** `SpatialAugmentations` uses `F.grid_sample` (PyTorch) internally; NIfTI-list and NumPy inputs are converted to PyTorch for processing and converted back. TensorFlow, JAX, and Keras are handled by the base-class automatic routing (~).
173
+ > **†** `Apodization[truncate]` and `ZeroFill` both change `N_PTS`. Because `target_pts` / `n_pts` is a module-level scalar, the **same length is applied uniformly to every batch member** — the output is still a uniform tensor.
117
174
 
175
+ > **‡** `RawProcessor[remove_outliers]` on tensor backends **zeroes outlier transients instead of dropping them** — a batched tensor cannot go ragged.
118
176
 
119
177
  ---
120
178
 
@@ -0,0 +1,41 @@
1
+ ####################################################################################################
2
+ # __init__.py #
3
+ ####################################################################################################
4
+ # #
5
+ # Authors: J. T. LaMaster (john.t.lamaster@gmail.com) #
6
+ # J. P. Merkofer (j.p.merkofer@tue.nl) #
7
+ # K. C. Igwe (kci2104@columbia.edu) #
8
+ # #
9
+ # Created: 2025-10-25 #
10
+ # #
11
+ # Purpose: Package entry point. Re-exports the public API: the Augmentrum container, the pipeline, #
12
+ # and every augmentation module. #
13
+ # #
14
+ ####################################################################################################
15
+
16
+ __version__ = "0.0.3"
17
+ __author__ = "John T. LaMaster, Julian P. Merkofer, Kay C. Igwe"
18
+ __email__ = "j.p.merkofer@tue.nl"
19
+
20
+ # Main API
21
+ #*************#
22
+ # imports #
23
+ #*************#
24
+ import nifti_mrs_plus
25
+
26
+ # Claim the NIfTI-MRS provenance record before anything can write one. The
27
+ # container is a shared transport, so left alone it would credit itself for
28
+ # work Augmentrum did.
29
+ nifti_mrs_plus.set_provenance("Augmentrum", __version__)
30
+
31
+ from augmentrum.core import Augmentrum, NIfTI_MRS_Plus, Backend, BaseModule, AugmentationPipeline # noqa: E402
32
+
33
+ __all__ = [
34
+ "__version__",
35
+ "Augmentrum",
36
+ "NIfTI_MRS_Plus",
37
+ "Backend",
38
+ "BaseModule",
39
+ "AugmentationPipeline",
40
+ ]
41
+
@@ -0,0 +1,56 @@
1
+ ####################################################################################################
2
+ # augmentation/__init__.py #
3
+ ####################################################################################################
4
+ # #
5
+ # Authors: K. C. Igwe (kci2104@columbia.edu) #
6
+ # J. P. Merkofer (j.p.merkofer@tue.nl) #
7
+ # #
8
+ # Created: 2026-02-13 #
9
+ # #
10
+ # Purpose: Re-exports the augmentation modules, each a BaseModule that perturbs MRS data in a #
11
+ # physically motivated way. #
12
+ # #
13
+ ####################################################################################################
14
+
15
+ #*************#
16
+ # imports #
17
+ #*************#
18
+ from augmentrum.augmentation.noise import Noise
19
+ from augmentrum.augmentation.line_broadening import LineBroadening
20
+ from augmentrum.augmentation.baseline_augmentation import BaselineAugmentation
21
+ from augmentrum.augmentation.residual_water import ResidualWater
22
+ from augmentrum.augmentation.spurious_echoes import SpuriousEchoes
23
+ from augmentrum.augmentation.artificial_peaks import ArtificialPeaks
24
+ from augmentrum.augmentation.eddy_current import EddyCurrent
25
+ from augmentrum.augmentation.apodization import Apodization
26
+ from augmentrum.augmentation.phase_frequency import PhaseShift, FrequencyShift
27
+ from augmentrum.augmentation.amplitude_scaling import AmplitudeScaling
28
+ from augmentrum.augmentation.spatial_augmentations import SpatialAugmentations
29
+ from augmentrum.augmentation.transient_synthesis import TransientSynthesizer
30
+ from augmentrum.augmentation.zero_fill import ZeroFill
31
+ from augmentrum.augmentation.macromolecules import (
32
+ Macromolecules, MMSource, Parametrized, SemiParametrized, Measured, Supplied,
33
+ )
34
+
35
+ __all__ = [
36
+ 'Noise',
37
+ 'LineBroadening',
38
+ 'BaselineAugmentation',
39
+ 'ResidualWater',
40
+ 'Macromolecules',
41
+ 'MMSource',
42
+ 'Parametrized',
43
+ 'SemiParametrized',
44
+ 'Measured',
45
+ 'Supplied',
46
+ 'SpuriousEchoes',
47
+ 'ArtificialPeaks',
48
+ 'EddyCurrent',
49
+ 'Apodization',
50
+ 'PhaseShift',
51
+ 'FrequencyShift',
52
+ 'AmplitudeScaling',
53
+ 'SpatialAugmentations',
54
+ 'TransientSynthesizer',
55
+ 'ZeroFill',
56
+ ]
@@ -16,18 +16,23 @@
16
16
  #*************#
17
17
  # imports #
18
18
  #*************#
19
+ from __future__ import annotations
20
+
19
21
  from typing import List, Optional, Union, Tuple
20
22
  import numpy as np
21
- from fsl_mrs.core import NIFTI_MRS
22
23
 
23
24
  from augmentrum.core.base_module import BaseModule
24
- from augmentrum.core.nifti_mrs_plus import Backend
25
- from augmentrum.utils.tensor_ops import match_backend
25
+ from augmentrum.processing.utils import to_backend
26
+ from nifti_mrs_plus import Backend
26
27
 
27
28
 
28
- ####################################################################################################
29
- # AmplitudeScaling Class #
30
- ####################################################################################################
29
+ #**************************************************************************************************#
30
+ # Class AmplitudeScaling #
31
+ #**************************************************************************************************#
32
+ # #
33
+ # Apply amplitude scaling to MRS data. #
34
+ # #
35
+ #**************************************************************************************************#
31
36
  class AmplitudeScaling(BaseModule):
32
37
  """
33
38
  Apply amplitude scaling to MRS data.
@@ -65,6 +70,12 @@ class AmplitudeScaling(BaseModule):
65
70
  SUPPORTED_BACKENDS = [Backend.NIFTI_LIST, Backend.NUMPY, Backend.PYTORCH,
66
71
  Backend.TENSORFLOW, Backend.JAX, Backend.KERAS]
67
72
 
73
+ # Acts on every coil and transient alike, so per-sample masks pass through.
74
+ MASKS = 'pass'
75
+
76
+ # A scale broadcasts, so a batch can carry one factor per sample.
77
+ PER_SAMPLE_PARAMS = ('scale_factor',)
78
+
68
79
  def __init__(
69
80
  self,
70
81
  scale_factor: Union[float, Tuple[float, float]] = 1.0,
@@ -73,29 +84,30 @@ class AmplitudeScaling(BaseModule):
73
84
  **kwargs
74
85
  ):
75
86
  """Initialize AmplitudeScaling module."""
76
- super().__init__(**kwargs)
87
+ super().__init__()
77
88
 
78
89
  self.scale_factor = scale_factor
79
90
  self.distribution = distribution
80
91
  self.seed = seed
81
- self.rng = np.random.default_rng(seed)
82
92
 
83
93
  # Validate distribution
84
94
  if distribution not in ['uniform', 'normal']:
85
95
  raise ValueError(f"distribution must be 'uniform' or 'normal', got {distribution}")
86
96
 
87
- def _get_scale_factor(self) -> float:
88
- """Get scale factor (sample if range is provided)."""
97
+ def _get_scale_factor(self, index: int = 0) -> float:
98
+ """Get scale factor (sample if a range, index if a per-sample vector)."""
99
+ if isinstance(self.scale_factor, np.ndarray) and self.scale_factor.ndim:
100
+ return float(self.sample_of(self.scale_factor, index))
89
101
  if isinstance(self.scale_factor, (tuple, list)):
90
102
  min_scale, max_scale = self.scale_factor
91
103
 
92
104
  if self.distribution == 'uniform':
93
- return self.rng.uniform(min_scale, max_scale)
105
+ return self.rng.numpy_rng().uniform(min_scale, max_scale)
94
106
  elif self.distribution == 'normal':
95
107
  # Use range as mean ± std
96
108
  mean = (min_scale + max_scale) / 2.0
97
109
  std = (max_scale - min_scale) / 4.0 # ~95% within range
98
- return self.rng.normal(mean, std)
110
+ return self.rng.numpy_rng().normal(mean, std)
99
111
  else:
100
112
  return float(self.scale_factor)
101
113
 
@@ -117,9 +129,9 @@ class AmplitudeScaling(BaseModule):
117
129
  """
118
130
  scaled_data = []
119
131
 
120
- for nifti in data_list:
121
- # Get scale factor (sample if random)
122
- scale = self._get_scale_factor()
132
+ for i, nifti in enumerate(data_list):
133
+ # Get scale factor (sample if random, index if per-sample)
134
+ scale = self._get_scale_factor(i)
123
135
 
124
136
  # Get data
125
137
  fid = nifti[:].copy()
@@ -154,14 +166,14 @@ class AmplitudeScaling(BaseModule):
154
166
  """
155
167
  # Get scale factor for each sample in batch
156
168
  batch_size = data.shape[0]
157
- scales = np.array([self._get_scale_factor() for _ in range(batch_size)])
169
+ scales = np.array([self._get_scale_factor(i) for i in range(batch_size)])
158
170
 
159
171
  # Reshape scales for broadcasting
160
172
  scale_shape = [batch_size] + [1] * (data.ndim - 1)
161
173
  scales = scales.reshape(scale_shape)
162
174
 
163
175
  # Apply scaling
164
- data_scaled = data * match_backend(scales, data)
176
+ data_scaled = data * to_backend(scales, data)
165
177
 
166
178
  return data_scaled, water
167
179