modusa 0.2.22__py3-none-any.whl → 0.2.23__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.
- modusa/decorators.py +4 -4
- modusa/devtools/docs/source/generators/audio_waveforms.rst +8 -0
- modusa/devtools/docs/source/generators/base.rst +8 -0
- modusa/devtools/docs/source/generators/index.rst +8 -0
- modusa/devtools/docs/source/io/audio_loader.rst +8 -0
- modusa/devtools/docs/source/io/base.rst +8 -0
- modusa/devtools/docs/source/io/index.rst +8 -0
- modusa/devtools/docs/source/plugins/base.rst +8 -0
- modusa/devtools/docs/source/plugins/index.rst +7 -0
- modusa/devtools/docs/source/signals/audio_signal.rst +8 -0
- modusa/devtools/docs/source/signals/base.rst +8 -0
- modusa/devtools/docs/source/signals/frequency_domain_signal.rst +8 -0
- modusa/devtools/docs/source/signals/index.rst +11 -0
- modusa/devtools/docs/source/signals/spectrogram.rst +8 -0
- modusa/devtools/docs/source/signals/time_domain_signal.rst +8 -0
- modusa/devtools/docs/source/tools/audio_converter.rst +8 -0
- modusa/devtools/docs/source/tools/audio_player.rst +8 -0
- modusa/devtools/docs/source/tools/base.rst +8 -0
- modusa/devtools/docs/source/tools/fourier_tranform.rst +8 -0
- modusa/devtools/docs/source/tools/index.rst +13 -0
- modusa/devtools/docs/source/tools/math_ops.rst +8 -0
- modusa/devtools/docs/source/tools/plotter.rst +8 -0
- modusa/devtools/docs/source/tools/youtube_downloader.rst +8 -0
- modusa/devtools/generate_doc_source.py +96 -0
- modusa/devtools/generate_template.py +8 -8
- modusa/devtools/main.py +3 -2
- modusa/devtools/templates/test.py +2 -3
- modusa/devtools/templates/{engine.py → tool.py} +3 -8
- modusa/generators/__init__.py +0 -2
- modusa/generators/audio_waveforms.py +22 -13
- modusa/generators/base.py +1 -1
- modusa/io/__init__.py +1 -5
- modusa/io/audio_loader.py +3 -33
- modusa/main.py +0 -30
- modusa/signals/__init__.py +1 -5
- modusa/signals/audio_signal.py +181 -124
- modusa/signals/base.py +1 -8
- modusa/signals/frequency_domain_signal.py +140 -93
- modusa/signals/spectrogram.py +197 -98
- modusa/signals/time_domain_signal.py +177 -74
- modusa/tools/__init__.py +2 -0
- modusa/{io → tools}/audio_converter.py +12 -4
- modusa/tools/audio_player.py +114 -0
- modusa/tools/base.py +43 -0
- modusa/tools/fourier_tranform.py +24 -0
- modusa/tools/math_ops.py +232 -0
- modusa/{io → tools}/plotter.py +155 -42
- modusa/{io → tools}/youtube_downloader.py +2 -2
- modusa/utils/excp.py +9 -42
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/METADATA +1 -1
- modusa-0.2.23.dist-info/RECORD +70 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +0 -3
- modusa/engines/base.py +0 -14
- modusa/io/audio_player.py +0 -72
- modusa/signals/signal_ops.py +0 -158
- modusa-0.2.22.dist-info/RECORD +0 -47
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/WHEEL +0 -0
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/licenses/LICENSE.md +0 -0
modusa/signals/spectrogram.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import immutable_property, validate_args_type
|
|
6
6
|
from modusa.signals.base import ModusaSignal
|
|
7
|
+
from modusa.tools.math_ops import MathOps
|
|
7
8
|
from typing import Self, Any
|
|
8
9
|
import numpy as np
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
@@ -33,35 +34,30 @@ class Spectrogram(ModusaSignal):
|
|
|
33
34
|
#----------------------------------
|
|
34
35
|
|
|
35
36
|
@validate_args_type()
|
|
36
|
-
def __init__(self, S: np.ndarray, f: np.ndarray,
|
|
37
|
-
super().__init__()
|
|
37
|
+
def __init__(self, S: np.ndarray, f: np.ndarray, frame_rate: float, t0: float = 0.0, title: str | None = None):
|
|
38
|
+
super().__init__()
|
|
38
39
|
|
|
39
40
|
if S.ndim != 2:
|
|
40
|
-
raise excp.InputValueError(f"`S` must have 2
|
|
41
|
+
raise excp.InputValueError(f"`S` must have 2 dimensions, got {S.ndim}.")
|
|
41
42
|
if f.ndim != 1:
|
|
42
43
|
raise excp.InputValueError(f"`f` must have 1 dimension, got {f.ndim}.")
|
|
43
|
-
if
|
|
44
|
-
raise excp.InputValueError(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
raise excp.InputValueError(f"`f` and `t` shape do not match with `S` {S.shape}, got {(f.shape[0], t.shape[0])}")
|
|
48
|
-
|
|
44
|
+
if f.shape[0] != S.shape[0]:
|
|
45
|
+
raise excp.InputValueError(
|
|
46
|
+
f"Shape mismatch between `S` and `f`: expected {S.shape[0]}, got {f.shape[0]}"
|
|
47
|
+
)
|
|
49
48
|
if S.shape[1] == 0:
|
|
50
|
-
raise excp.InputValueError("`S` must have at least one time frame")
|
|
49
|
+
raise excp.InputValueError("`S` must have at least one time frame.")
|
|
51
50
|
|
|
52
|
-
if t.shape[0] >= 2:
|
|
53
|
-
dts = np.diff(t)
|
|
54
|
-
if not np.allclose(dts, dts[0]):
|
|
55
|
-
raise excp.InputValueError("`t` must be equally spaced")
|
|
56
|
-
|
|
57
51
|
self._S = S
|
|
58
52
|
self._f = f
|
|
59
|
-
self.
|
|
53
|
+
self._frame_rate = float(frame_rate)
|
|
54
|
+
self._t0 = float(t0)
|
|
60
55
|
self.title = title or self._name
|
|
61
|
-
|
|
56
|
+
|
|
62
57
|
#----------------------
|
|
63
58
|
# Properties
|
|
64
59
|
#----------------------
|
|
60
|
+
|
|
65
61
|
@immutable_property("Create a new object instead.")
|
|
66
62
|
def S(self) -> np.ndarray:
|
|
67
63
|
"""Spectrogram matrix (freq × time)."""
|
|
@@ -72,10 +68,25 @@ class Spectrogram(ModusaSignal):
|
|
|
72
68
|
"""Frequency axis."""
|
|
73
69
|
return self._f
|
|
74
70
|
|
|
71
|
+
@immutable_property("Create a new object instead.")
|
|
72
|
+
def frame_rate(self) -> np.ndarray:
|
|
73
|
+
"""Frequency axis."""
|
|
74
|
+
return self._frame_rate
|
|
75
|
+
|
|
76
|
+
@immutable_property("Create a new object instead.")
|
|
77
|
+
def t0(self) -> np.ndarray:
|
|
78
|
+
"""Frequency axis."""
|
|
79
|
+
return self._t0
|
|
80
|
+
|
|
81
|
+
#----------------------
|
|
82
|
+
# Derived Properties
|
|
83
|
+
#----------------------
|
|
84
|
+
|
|
75
85
|
@immutable_property("Create a new object instead.")
|
|
76
86
|
def t(self) -> np.ndarray:
|
|
77
87
|
"""Time axis."""
|
|
78
|
-
|
|
88
|
+
n_frames = self._S.shape[1]
|
|
89
|
+
return np.arange(n_frames) / self.frame_rate + self.t0
|
|
79
90
|
|
|
80
91
|
@immutable_property("Read only property.")
|
|
81
92
|
def shape(self) -> np.ndarray:
|
|
@@ -87,12 +98,51 @@ class Spectrogram(ModusaSignal):
|
|
|
87
98
|
"""Number of dimensions (always 2)."""
|
|
88
99
|
return self.S.ndim
|
|
89
100
|
|
|
90
|
-
@
|
|
91
|
-
def
|
|
101
|
+
@property
|
|
102
|
+
def magnitude(self) -> "Spectrogram":
|
|
103
|
+
"""Return a new Spectrogram with magnitude values."""
|
|
104
|
+
mag = np.abs(self.S)
|
|
105
|
+
return self.__class__(S=mag, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def power(self) -> "Spectrogram":
|
|
109
|
+
"""Return a new Spectrogram with power (magnitude squared)."""
|
|
110
|
+
power = np.abs(self.S) ** 2
|
|
111
|
+
return self.__class__(S=power, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def angle(self) -> "Spectrogram":
|
|
115
|
+
"""Return a new Spectrogram with phase angle (in radians)."""
|
|
116
|
+
angle = np.angle(self.S)
|
|
117
|
+
return self.__class__(S=angle, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def real(self) -> "Spectrogram":
|
|
121
|
+
"""Return a new Spectrogram with real part."""
|
|
122
|
+
return self.__class__(S=self.S.real, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def imag(self) -> "Spectrogram":
|
|
126
|
+
"""Return a new Spectrogram with imaginary part."""
|
|
127
|
+
return self.__class__(S=self.S.imag, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def phase(self) -> "Spectrogram":
|
|
131
|
+
"""Return a new Spectrogram with normalized phase."""
|
|
132
|
+
phase = self.S / (np.abs(self.S) + 1e-10) # Avoid division by zero
|
|
133
|
+
return self.__class__(S=phase, f=self.f, frame_rate=self.frame_rate, title=self.title)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
#------------------------
|
|
138
|
+
# Useful tools
|
|
139
|
+
#------------------------
|
|
140
|
+
|
|
141
|
+
def print_info(self) -> None:
|
|
92
142
|
"""Print key information about the spectrogram signal."""
|
|
93
|
-
time_resolution =
|
|
143
|
+
time_resolution = 1.0 / self.frame_rate
|
|
94
144
|
n_freq_bins = self.S.shape[0]
|
|
95
|
-
|
|
145
|
+
|
|
96
146
|
# Estimate NFFT size
|
|
97
147
|
nfft = (n_freq_bins - 1) * 2
|
|
98
148
|
|
|
@@ -100,15 +150,10 @@ class Spectrogram(ModusaSignal):
|
|
|
100
150
|
print(f"{'Title':<20}: {self.title}")
|
|
101
151
|
print(f"{'Kind':<20}: {self._name}")
|
|
102
152
|
print(f"{'Shape':<20}: {self.S.shape} (freq bins × time frames)")
|
|
153
|
+
print(f"{'Frame Rate':<20}: {self.frame_rate} (frames / sec)")
|
|
103
154
|
print(f"{'Time resolution':<20}: {time_resolution:.4f} sec ({time_resolution * 1000:.2f} ms)")
|
|
104
155
|
print(f"{'Freq resolution':<20}: {(self.f[1] - self.f[0]):.2f} Hz")
|
|
105
156
|
print("-"*50)
|
|
106
|
-
#------------------------
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
#------------------------
|
|
110
|
-
# Useful tools
|
|
111
|
-
#------------------------
|
|
112
157
|
|
|
113
158
|
def __getitem__(self, key: tuple[int | slice, int | slice]) -> "Spectrogram | FrequencyDomainSignal | TimeDomainSignal":
|
|
114
159
|
"""
|
|
@@ -139,12 +184,7 @@ class Spectrogram(ModusaSignal):
|
|
|
139
184
|
sliced_data = np.asarray(sliced_data).flatten()
|
|
140
185
|
sliced_f = np.asarray(sliced_f)
|
|
141
186
|
t0 = float(self.t[t_key])
|
|
142
|
-
return FrequencyDomainSignal(
|
|
143
|
-
spectrum=sliced_data,
|
|
144
|
-
f=sliced_f,
|
|
145
|
-
t0=t0,
|
|
146
|
-
title=self.title + f" [t = {t0:.2f} sec]"
|
|
147
|
-
)
|
|
187
|
+
return FrequencyDomainSignal(spectrum=sliced_data, f=sliced_f, t0=t0, title=self.title + f" [t = {t0:.2f} sec]")
|
|
148
188
|
|
|
149
189
|
# Case 3: time slice at a single frequency (→ TimeDomainSignal)
|
|
150
190
|
elif isinstance(f_key, int):
|
|
@@ -153,21 +193,11 @@ class Spectrogram(ModusaSignal):
|
|
|
153
193
|
sr = 1.0 / np.mean(np.diff(self.t)) # assume uniform time axis
|
|
154
194
|
t0 = float(self.t[0])
|
|
155
195
|
f_val = float(self.f[f_key])
|
|
156
|
-
return TimeDomainSignal(
|
|
157
|
-
y=sliced_data,
|
|
158
|
-
sr=sr,
|
|
159
|
-
t0=t0,
|
|
160
|
-
title=self.title + f" [f = {f_val:.2f} Hz]"
|
|
161
|
-
)
|
|
196
|
+
return TimeDomainSignal(y=sliced_data, sr=sr, t0=t0, title=self.title + f" [f = {f_val:.2f} Hz]")
|
|
162
197
|
|
|
163
198
|
# Case 4: 2D slice → Spectrogram
|
|
164
199
|
else:
|
|
165
|
-
return self.__class__(
|
|
166
|
-
S=sliced_data,
|
|
167
|
-
f=sliced_f,
|
|
168
|
-
t=sliced_t,
|
|
169
|
-
title=self.title
|
|
170
|
-
)
|
|
200
|
+
return self.__class__(S=sliced_data, f=sliced_f, frame_rate=self.frame_rate, t0=sliced_t[0], title=self.title)
|
|
171
201
|
|
|
172
202
|
raise TypeError("Expected 2D indexing: signal[f_idx, t_idx]")
|
|
173
203
|
|
|
@@ -215,12 +245,11 @@ class Spectrogram(ModusaSignal):
|
|
|
215
245
|
cropped_f = f[f_mask]
|
|
216
246
|
cropped_t = t[t_mask]
|
|
217
247
|
|
|
218
|
-
return self.__class__(S=cropped_S, f=cropped_f,
|
|
248
|
+
return self.__class__(S=cropped_S, f=cropped_f, frame_rate=self.frame_rate, t0=cropped_t[0], title=self.title)
|
|
219
249
|
|
|
220
250
|
|
|
221
251
|
def plot(
|
|
222
252
|
self,
|
|
223
|
-
log_compression_factor: int | float | None = None,
|
|
224
253
|
ax: plt.Axes | None = None,
|
|
225
254
|
cmap: str = "gray_r",
|
|
226
255
|
title: str | None = None,
|
|
@@ -230,7 +259,10 @@ class Spectrogram(ModusaSignal):
|
|
|
230
259
|
ylim: tuple[float, float] | None = None,
|
|
231
260
|
xlim: tuple[float, float] | None = None,
|
|
232
261
|
highlight: list[tuple[float, float, float, float]] | None = None,
|
|
262
|
+
vlines: list | None = None,
|
|
263
|
+
hlines: list | None = None,
|
|
233
264
|
origin: str = "lower", # or "lower"
|
|
265
|
+
gamma: int | float | None = None,
|
|
234
266
|
show_colorbar: bool = True,
|
|
235
267
|
cax: plt.Axes | None = None,
|
|
236
268
|
show_grid: bool = True,
|
|
@@ -284,7 +316,7 @@ class Spectrogram(ModusaSignal):
|
|
|
284
316
|
matplotlib.figure.Figure
|
|
285
317
|
The figure object containing the plot.
|
|
286
318
|
"""
|
|
287
|
-
from modusa.
|
|
319
|
+
from modusa.tools.plotter import Plotter
|
|
288
320
|
|
|
289
321
|
title = title or self.title
|
|
290
322
|
|
|
@@ -292,7 +324,6 @@ class Spectrogram(ModusaSignal):
|
|
|
292
324
|
M=self.S,
|
|
293
325
|
r=self.f,
|
|
294
326
|
c=self.t,
|
|
295
|
-
log_compression_factor=log_compression_factor,
|
|
296
327
|
ax=ax,
|
|
297
328
|
cmap=cmap,
|
|
298
329
|
title=title,
|
|
@@ -302,7 +333,10 @@ class Spectrogram(ModusaSignal):
|
|
|
302
333
|
rlim=ylim,
|
|
303
334
|
clim=xlim,
|
|
304
335
|
highlight=highlight,
|
|
336
|
+
vlines=vlines,
|
|
337
|
+
hlines=hlines,
|
|
305
338
|
origin=origin,
|
|
339
|
+
gamma=gamma,
|
|
306
340
|
show_colorbar=show_colorbar,
|
|
307
341
|
cax=cax,
|
|
308
342
|
show_grid=show_grid,
|
|
@@ -321,114 +355,179 @@ class Spectrogram(ModusaSignal):
|
|
|
321
355
|
|
|
322
356
|
def __add__(self, other):
|
|
323
357
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
324
|
-
result =
|
|
325
|
-
return self.__class__(S=result, f=self.f,
|
|
358
|
+
result = MathOps.add(self.S, other_data)
|
|
359
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
326
360
|
|
|
327
361
|
def __radd__(self, other):
|
|
328
|
-
result =
|
|
329
|
-
return self.__class__(S=result, f=self.f,
|
|
362
|
+
result = MathOps.add(other, self.S)
|
|
363
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
330
364
|
|
|
331
365
|
def __sub__(self, other):
|
|
332
366
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
333
|
-
result =
|
|
334
|
-
return self.__class__(S=result, f=self.f,
|
|
367
|
+
result = MathOps.subtract(self.S, other_data)
|
|
368
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
335
369
|
|
|
336
370
|
def __rsub__(self, other):
|
|
337
|
-
result =
|
|
338
|
-
return self.__class__(S=result, f=self.f,
|
|
371
|
+
result = MathOps.subtract(other, self.S)
|
|
372
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
339
373
|
|
|
340
374
|
def __mul__(self, other):
|
|
341
375
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
342
|
-
result =
|
|
343
|
-
return self.__class__(S=result, f=self.f,
|
|
376
|
+
result = MathOps.multiply(self.S, other_data)
|
|
377
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
344
378
|
|
|
345
379
|
def __rmul__(self, other):
|
|
346
|
-
result =
|
|
347
|
-
return self.__class__(S=result, f=self.f,
|
|
380
|
+
result = MathOps.multiply(other, self.S)
|
|
381
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
348
382
|
|
|
349
383
|
def __truediv__(self, other):
|
|
350
384
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
351
|
-
result =
|
|
352
|
-
return self.__class__(S=result, f=self.f,
|
|
385
|
+
result = MathOps.true_divide(self.S, other_data)
|
|
386
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
353
387
|
|
|
354
388
|
def __rtruediv__(self, other):
|
|
355
|
-
result =
|
|
356
|
-
return self.__class__(S=result, f=self.f,
|
|
389
|
+
result = MathOps.true_divide(other, self.S)
|
|
390
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
357
391
|
|
|
358
392
|
def __floordiv__(self, other):
|
|
359
393
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
360
|
-
result =
|
|
361
|
-
return self.__class__(S=result, f=self.f,
|
|
394
|
+
result = MathOps.floor_divide(self.S, other_data)
|
|
395
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
362
396
|
|
|
363
397
|
def __rfloordiv__(self, other):
|
|
364
|
-
result =
|
|
365
|
-
return self.__class__(S=result, f=self.f,
|
|
398
|
+
result = MathOps.floor_divide(other, self.S)
|
|
399
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
366
400
|
|
|
367
401
|
def __pow__(self, other):
|
|
368
402
|
other_data = other.S if isinstance(other, self.__class__) else other
|
|
369
|
-
result =
|
|
370
|
-
return self.__class__(S=result, f=self.f,
|
|
403
|
+
result = MathOps.power(self.S, other_data)
|
|
404
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
371
405
|
|
|
372
406
|
def __rpow__(self, other):
|
|
373
|
-
result =
|
|
374
|
-
return self.__class__(S=result, f=self.f,
|
|
407
|
+
result = MathOps.power(other, self.S)
|
|
408
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
375
409
|
|
|
376
410
|
def __abs__(self):
|
|
377
|
-
result =
|
|
378
|
-
return self.__class__(S=result, f=self.f,
|
|
411
|
+
result = MathOps.abs(self.S)
|
|
412
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
413
|
+
|
|
414
|
+
#--------------------------
|
|
415
|
+
# Other signal ops
|
|
416
|
+
#--------------------------
|
|
379
417
|
|
|
380
418
|
def sin(self):
|
|
381
419
|
"""Element-wise sine of the spectrogram."""
|
|
382
|
-
|
|
420
|
+
result = MathOps.sin(self.S)
|
|
421
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
383
422
|
|
|
384
423
|
def cos(self):
|
|
385
424
|
"""Element-wise cosine of the spectrogram."""
|
|
386
|
-
|
|
425
|
+
result = MathOps.cos(self.S)
|
|
426
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
387
427
|
|
|
388
428
|
def exp(self):
|
|
389
429
|
"""Element-wise exponential of the spectrogram."""
|
|
390
|
-
|
|
430
|
+
result = MathOps.exp(self.S)
|
|
431
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
391
432
|
|
|
392
433
|
def tanh(self):
|
|
393
434
|
"""Element-wise hyperbolic tangent of the spectrogram."""
|
|
394
|
-
|
|
435
|
+
result = MathOps.tanh(self.S)
|
|
436
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
395
437
|
|
|
396
438
|
def log(self):
|
|
397
439
|
"""Element-wise natural logarithm of the spectrogram."""
|
|
398
|
-
|
|
440
|
+
result = MathOps.log(self.S)
|
|
441
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
399
442
|
|
|
400
443
|
def log1p(self):
|
|
401
444
|
"""Element-wise log(1 + M) of the spectrogram."""
|
|
402
|
-
|
|
445
|
+
result = MathOps.log1p(self.S)
|
|
446
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
403
447
|
|
|
404
448
|
def log10(self):
|
|
405
449
|
"""Element-wise base-10 logarithm of the spectrogram."""
|
|
406
|
-
|
|
450
|
+
result = MathOps.log10(self.S)
|
|
451
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
407
452
|
|
|
408
453
|
def log2(self):
|
|
409
454
|
"""Element-wise base-2 logarithm of the spectrogram."""
|
|
410
|
-
|
|
455
|
+
result = MathOps.log2(self.S)
|
|
456
|
+
return self.__class__(S=result, f=self.f, frame_rate=self.frame_rate, t0=self.t0, title=self.title)
|
|
411
457
|
|
|
458
|
+
#--------------------------
|
|
459
|
+
# Aggregation signal ops
|
|
460
|
+
#--------------------------
|
|
412
461
|
|
|
413
|
-
def mean(self) -> float:
|
|
462
|
+
def mean(self, axis: int | None = None) -> float:
|
|
414
463
|
"""Return the mean of the spectrogram values."""
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
464
|
+
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
465
|
+
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
466
|
+
result = MathOps.mean(self.S, axis=axis)
|
|
467
|
+
if axis == 0: # Aggregating across rows
|
|
468
|
+
return TimeDomainSignal(y=result, sr=self.frame_rate, t0=self.t0, title=self.title)
|
|
469
|
+
elif axis in [1, -1]:
|
|
470
|
+
return FrequencyDomainSignal(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
471
|
+
elif axis is None:
|
|
472
|
+
return result
|
|
473
|
+
else:
|
|
474
|
+
raise excp.InputValueError("Can't perform mean operation")
|
|
475
|
+
|
|
476
|
+
def std(self, axis: int | None = None) -> float:
|
|
418
477
|
"""Return the standard deviation of the spectrogram values."""
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
478
|
+
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
479
|
+
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
480
|
+
result = MathOps.std(self.S, axis=axis)
|
|
481
|
+
if axis == 0: # Aggregating across rows
|
|
482
|
+
return TimeDomainSignal(y=result, sr=self.frame_rate, t0=self.t0, title=self.title)
|
|
483
|
+
elif axis in [1, -1]:
|
|
484
|
+
return FrequencyDomainSignal(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
485
|
+
elif axis is None:
|
|
486
|
+
return result
|
|
487
|
+
else:
|
|
488
|
+
raise excp.InputValueError("Can't perform std operation")
|
|
489
|
+
|
|
490
|
+
def min(self, axis: int | None = None) -> float:
|
|
422
491
|
"""Return the minimum value in the spectrogram."""
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
492
|
+
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
493
|
+
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
494
|
+
result = MathOps.min(self.S, axis=axis)
|
|
495
|
+
if axis == 0: # Aggregating across rows
|
|
496
|
+
return TimeDomainSignal(y=result, sr=self.frame_rate, t0=self.t0, title=self.title)
|
|
497
|
+
elif axis in [1, -1]:
|
|
498
|
+
return FrequencyDomainSignal(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
499
|
+
elif axis is None:
|
|
500
|
+
return result
|
|
501
|
+
else:
|
|
502
|
+
raise excp.InputValueError("Can't perform min operation")
|
|
503
|
+
|
|
504
|
+
def max(self, axis: int | None = None) -> float:
|
|
426
505
|
"""Return the maximum value in the spectrogram."""
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
506
|
+
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
507
|
+
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
508
|
+
result = MathOps.max(self.S, axis=axis)
|
|
509
|
+
if axis == 0: # Aggregating across rows
|
|
510
|
+
return TimeDomainSignal(y=result, sr=self.frame_rate, t0=self.t0, title=self.title)
|
|
511
|
+
elif axis in [1, -1]:
|
|
512
|
+
return FrequencyDomainSignal(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
513
|
+
elif axis is None:
|
|
514
|
+
return result
|
|
515
|
+
else:
|
|
516
|
+
raise excp.InputValueError("Can't perform max operation")
|
|
517
|
+
|
|
518
|
+
def sum(self, axis: int | None = None) -> float:
|
|
430
519
|
"""Return the sum of the spectrogram values."""
|
|
431
|
-
|
|
520
|
+
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
521
|
+
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
522
|
+
result = MathOps.sum(self.S, axis=axis)
|
|
523
|
+
if axis == 0: # Aggregating across rows
|
|
524
|
+
return TimeDomainSignal(y=result, sr=self.frame_rate, t0=self.t0, title=self.title)
|
|
525
|
+
elif axis in [1, -1]:
|
|
526
|
+
return FrequencyDomainSignal(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
527
|
+
elif axis is None:
|
|
528
|
+
return result
|
|
529
|
+
else:
|
|
530
|
+
raise excp.InputValueError("Can't perform sum operation")
|
|
432
531
|
|
|
433
532
|
#-----------------------------------
|
|
434
533
|
# Repr
|