authtransforms 0.1.0__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.
- authtransforms/__init__.py +65 -0
- authtransforms/pipeline.py +196 -0
- authtransforms/transforms.py +648 -0
- authtransforms/utils.py +364 -0
- authtransforms-0.1.0.dist-info/METADATA +438 -0
- authtransforms-0.1.0.dist-info/RECORD +8 -0
- authtransforms-0.1.0.dist-info/WHEEL +4 -0
- authtransforms-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
authtransforms — Audio augmentation pipeline for PyTorch.
|
|
3
|
+
|
|
4
|
+
Inspired by: https://jonathanbgn.com/2021/08/30/audio-augmentation.html
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .pipeline import Compose, Identity, Lambda, OneOf, RandomOrder, SomeOf
|
|
8
|
+
from .transforms import (
|
|
9
|
+
AddGaussianNoise,
|
|
10
|
+
Normalize,
|
|
11
|
+
RandomApply,
|
|
12
|
+
RandomBackgroundNoise,
|
|
13
|
+
RandomClip,
|
|
14
|
+
RandomGain,
|
|
15
|
+
RandomPitchShift,
|
|
16
|
+
RandomSpeedChange,
|
|
17
|
+
SpecAugment,
|
|
18
|
+
TimeShift,
|
|
19
|
+
ToMono,
|
|
20
|
+
RoomImpulseResponse,
|
|
21
|
+
TimeStretch,
|
|
22
|
+
)
|
|
23
|
+
from .utils import (
|
|
24
|
+
audio_info,
|
|
25
|
+
compare_audio,
|
|
26
|
+
compare_play,
|
|
27
|
+
play_audio,
|
|
28
|
+
plot_audio,
|
|
29
|
+
plot_spectrogram,
|
|
30
|
+
plot_waveform,
|
|
31
|
+
save_audio,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Pipeline combinators
|
|
36
|
+
"Compose",
|
|
37
|
+
"OneOf",
|
|
38
|
+
"SomeOf",
|
|
39
|
+
"RandomOrder",
|
|
40
|
+
"Identity",
|
|
41
|
+
"Lambda",
|
|
42
|
+
# Transforms
|
|
43
|
+
"RandomClip",
|
|
44
|
+
"RandomSpeedChange",
|
|
45
|
+
"RandomBackgroundNoise",
|
|
46
|
+
"RandomPitchShift",
|
|
47
|
+
"RandomGain",
|
|
48
|
+
"AddGaussianNoise",
|
|
49
|
+
"TimeShift",
|
|
50
|
+
"SpecAugment",
|
|
51
|
+
"RandomApply",
|
|
52
|
+
"Normalize",
|
|
53
|
+
"ToMono",
|
|
54
|
+
"RoomImpulseResponse",
|
|
55
|
+
# Utilities
|
|
56
|
+
"plot_waveform",
|
|
57
|
+
"plot_spectrogram",
|
|
58
|
+
"plot_audio",
|
|
59
|
+
"compare_audio",
|
|
60
|
+
"play_audio",
|
|
61
|
+
"compare_play",
|
|
62
|
+
"audio_info",
|
|
63
|
+
"save_audio",
|
|
64
|
+
"TimeStretch",
|
|
65
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Audio augmentation pipeline — a torchvision-style Compose for audio tensors.
|
|
3
|
+
|
|
4
|
+
Example
|
|
5
|
+
-------
|
|
6
|
+
>>> from transforms import RandomClip, RandomSpeedChange, AddGaussianNoise
|
|
7
|
+
>>> from pipeline import Compose, OneOf
|
|
8
|
+
>>>
|
|
9
|
+
>>> pipeline = Compose([
|
|
10
|
+
... RandomClip(sample_rate=16000, clip_length=16000 * 4),
|
|
11
|
+
... OneOf([
|
|
12
|
+
... RandomSpeedChange(sample_rate=16000),
|
|
13
|
+
... AddGaussianNoise(),
|
|
14
|
+
... ]),
|
|
15
|
+
... ])
|
|
16
|
+
>>> augmented = pipeline(audio_tensor)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import random
|
|
20
|
+
from typing import Callable, List, Optional, Sequence
|
|
21
|
+
import torch
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Compose:
|
|
25
|
+
"""Apply a sequence of transforms in order — mirrors torchvision.transforms.Compose.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
transforms: List of callables that accept and return a Tensor.
|
|
29
|
+
|
|
30
|
+
Example::
|
|
31
|
+
|
|
32
|
+
pipeline = Compose([
|
|
33
|
+
RandomClip(sample_rate, clip_length=64000),
|
|
34
|
+
RandomSpeedChange(sample_rate),
|
|
35
|
+
RandomBackgroundNoise(sample_rate, './noises'),
|
|
36
|
+
])
|
|
37
|
+
augmented = pipeline(audio)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, transforms: List[Callable]):
|
|
41
|
+
|
|
42
|
+
# The class where all the transforms are applied to the audio
|
|
43
|
+
|
|
44
|
+
# The List[Callable] indicates that the transforms variable should have a list of callable funcations
|
|
45
|
+
self.transforms = transforms
|
|
46
|
+
|
|
47
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor:
|
|
48
|
+
for t in self.transforms:
|
|
49
|
+
audio = t(audio)
|
|
50
|
+
return audio
|
|
51
|
+
|
|
52
|
+
def __repr__(self) -> str:
|
|
53
|
+
lines = [f"{self.__class__.__name__}("]
|
|
54
|
+
for t in self.transforms:
|
|
55
|
+
lines.append(f" {t},")
|
|
56
|
+
lines.append(")")
|
|
57
|
+
return "\n".join(lines)
|
|
58
|
+
|
|
59
|
+
def __len__(self) -> int:
|
|
60
|
+
return len(self.transforms)
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, idx):
|
|
63
|
+
return self.transforms[idx]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class OneOf:
|
|
67
|
+
"""Apply exactly one randomly-chosen transform from the list.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
transforms: List of transforms to choose from.
|
|
71
|
+
weights: Optional probability weights (unnormalized). If None, uniform.
|
|
72
|
+
|
|
73
|
+
Example::
|
|
74
|
+
|
|
75
|
+
augment = OneOf([
|
|
76
|
+
RandomSpeedChange(sample_rate),
|
|
77
|
+
AddGaussianNoise(),
|
|
78
|
+
RandomPitchShift(sample_rate),
|
|
79
|
+
])
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, transforms: List[Callable], weights: Optional[List[float]] = None): #The weights of randomly chosen transforms are stored
|
|
83
|
+
self.transforms = transforms
|
|
84
|
+
self.weights = weights
|
|
85
|
+
|
|
86
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor:
|
|
87
|
+
if self.weights:
|
|
88
|
+
t = random.choices(self.transforms, weights=self.weights, k=1)[0] #if there are weights
|
|
89
|
+
else:
|
|
90
|
+
t = random.choice(self.transforms) #if there's no weight
|
|
91
|
+
return t(audio)
|
|
92
|
+
|
|
93
|
+
def __repr__(self) -> str:
|
|
94
|
+
lines = [f"{self.__class__.__name__}("]
|
|
95
|
+
for t in self.transforms:
|
|
96
|
+
lines.append(f" {t},")
|
|
97
|
+
lines.append(")")
|
|
98
|
+
return "\n".join(lines)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SomeOf:
|
|
102
|
+
"""Apply a random subset of transforms (n chosen without replacement).
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
transforms: Pool of available transforms.
|
|
106
|
+
n: Number of transforms to apply each call.
|
|
107
|
+
shuffle: If True, apply the selected transforms in a random order.
|
|
108
|
+
|
|
109
|
+
Example::
|
|
110
|
+
|
|
111
|
+
augment = SomeOf([
|
|
112
|
+
RandomGain(),
|
|
113
|
+
AddGaussianNoise(),
|
|
114
|
+
TimeShift(),
|
|
115
|
+
RandomPitchShift(sample_rate),
|
|
116
|
+
], n=2)
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# chooses n random transforms from a list and applies them sequentially to an audio tensor.
|
|
120
|
+
|
|
121
|
+
def __init__(self, transforms: List[Callable], n: int = 2, shuffle: bool = True):
|
|
122
|
+
if n > len(transforms):
|
|
123
|
+
raise ValueError(f"n={n} exceeds number of transforms ({len(transforms)})")
|
|
124
|
+
self.transforms = transforms
|
|
125
|
+
self.n = n
|
|
126
|
+
self.shuffle = shuffle
|
|
127
|
+
|
|
128
|
+
# Makes the object callable like a function
|
|
129
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor:
|
|
130
|
+
selected = random.sample(self.transforms, self.n) #selects n random transforms
|
|
131
|
+
if self.shuffle:
|
|
132
|
+
random.shuffle(selected)
|
|
133
|
+
for t in selected:
|
|
134
|
+
audio = t(audio)
|
|
135
|
+
return audio
|
|
136
|
+
|
|
137
|
+
def __repr__(self) -> str:
|
|
138
|
+
lines = [f"{self.__class__.__name__}(n={self.n},"]
|
|
139
|
+
for t in self.transforms:
|
|
140
|
+
lines.append(f" {t},")
|
|
141
|
+
lines.append(")")
|
|
142
|
+
return "\n".join(lines)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class RandomOrder:
|
|
146
|
+
"""Apply all transforms in a random order each call.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
transforms: List of transforms.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(self, transforms: List[Callable]):
|
|
153
|
+
self.transforms = transforms
|
|
154
|
+
|
|
155
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor:
|
|
156
|
+
order = list(self.transforms) # The transforms is copied in the order
|
|
157
|
+
random.shuffle(order) # Then shuffled and applied to the audio
|
|
158
|
+
for t in order:
|
|
159
|
+
audio = t(audio)
|
|
160
|
+
return audio
|
|
161
|
+
|
|
162
|
+
def __repr__(self) -> str:
|
|
163
|
+
lines = [f"{self.__class__.__name__}("]
|
|
164
|
+
for t in self.transforms:
|
|
165
|
+
lines.append(f" {t},")
|
|
166
|
+
lines.append(")")
|
|
167
|
+
return "\n".join(lines)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Identity:
|
|
171
|
+
"""Pass-through transform — returns the audio unchanged. Useful as a no-op placeholder."""
|
|
172
|
+
|
|
173
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor: #Identity is a transform that returns the input audio unchanged, mainly used as a placeholder or optional "do nothing" step in augmentation pipelines.
|
|
174
|
+
return audio
|
|
175
|
+
|
|
176
|
+
def __repr__(self) -> str:
|
|
177
|
+
return f"{self.__class__.__name__}()"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Lambda:
|
|
181
|
+
"""Wrap any callable as a named transform (mirrors torchvision.transforms.Lambda).
|
|
182
|
+
|
|
183
|
+
Example::
|
|
184
|
+
|
|
185
|
+
double = Lambda(lambda x: x * 2, name="Double")
|
|
186
|
+
"""
|
|
187
|
+
# To add custom augmentation and convert it into a transform
|
|
188
|
+
def __init__(self, func: Callable, name: str = "Lambda"):
|
|
189
|
+
self.func = func
|
|
190
|
+
self.name = name
|
|
191
|
+
|
|
192
|
+
def __call__(self, audio: torch.Tensor) -> torch.Tensor:
|
|
193
|
+
return self.func(audio)
|
|
194
|
+
|
|
195
|
+
def __repr__(self) -> str:
|
|
196
|
+
return f"{self.__class__.__name__}({self.name})"
|