modusa 0.2.23__py3-none-any.whl → 0.3__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/.DS_Store +0 -0
- modusa/__init__.py +8 -1
- modusa/devtools/{generate_doc_source.py → generate_docs_source.py} +5 -5
- modusa/devtools/generate_template.py +5 -5
- modusa/devtools/main.py +3 -3
- modusa/devtools/templates/generator.py +1 -1
- modusa/devtools/templates/io.py +1 -1
- modusa/devtools/templates/{signal.py → model.py} +18 -11
- modusa/devtools/templates/plugin.py +1 -1
- modusa/generators/__init__.py +11 -1
- modusa/generators/audio.py +188 -0
- modusa/generators/audio_waveforms.py +1 -1
- modusa/generators/base.py +1 -1
- modusa/generators/ftds.py +298 -0
- modusa/generators/s1d.py +270 -0
- modusa/generators/s2d.py +300 -0
- modusa/generators/s_ax.py +102 -0
- modusa/generators/t_ax.py +64 -0
- modusa/generators/tds.py +267 -0
- modusa/models/__init__.py +14 -0
- modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- modusa/models/audio.py +90 -0
- modusa/models/base.py +70 -0
- modusa/models/data.py +457 -0
- modusa/models/ftds.py +584 -0
- modusa/models/s1d.py +578 -0
- modusa/models/s2d.py +619 -0
- modusa/models/s_ax.py +448 -0
- modusa/models/t_ax.py +335 -0
- modusa/models/tds.py +465 -0
- modusa/plugins/__init__.py +3 -1
- modusa/tmp.py +98 -0
- modusa/tools/__init__.py +5 -0
- modusa/tools/audio_converter.py +56 -67
- modusa/tools/audio_loader.py +90 -0
- modusa/tools/audio_player.py +42 -67
- modusa/tools/math_ops.py +104 -1
- modusa/tools/plotter.py +305 -497
- modusa/tools/youtube_downloader.py +31 -98
- modusa/utils/excp.py +6 -0
- modusa/utils/np_func_cat.py +44 -0
- modusa/utils/plot.py +142 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
- modusa-0.3.dist-info/RECORD +60 -0
- modusa/devtools/docs/source/generators/audio_waveforms.rst +0 -8
- modusa/devtools/docs/source/generators/base.rst +0 -8
- modusa/devtools/docs/source/generators/index.rst +0 -8
- modusa/devtools/docs/source/io/audio_loader.rst +0 -8
- modusa/devtools/docs/source/io/base.rst +0 -8
- modusa/devtools/docs/source/io/index.rst +0 -8
- modusa/devtools/docs/source/plugins/base.rst +0 -8
- modusa/devtools/docs/source/plugins/index.rst +0 -7
- modusa/devtools/docs/source/signals/audio_signal.rst +0 -8
- modusa/devtools/docs/source/signals/base.rst +0 -8
- modusa/devtools/docs/source/signals/frequency_domain_signal.rst +0 -8
- modusa/devtools/docs/source/signals/index.rst +0 -11
- modusa/devtools/docs/source/signals/spectrogram.rst +0 -8
- modusa/devtools/docs/source/signals/time_domain_signal.rst +0 -8
- modusa/devtools/docs/source/tools/audio_converter.rst +0 -8
- modusa/devtools/docs/source/tools/audio_player.rst +0 -8
- modusa/devtools/docs/source/tools/base.rst +0 -8
- modusa/devtools/docs/source/tools/fourier_tranform.rst +0 -8
- modusa/devtools/docs/source/tools/index.rst +0 -13
- modusa/devtools/docs/source/tools/math_ops.rst +0 -8
- modusa/devtools/docs/source/tools/plotter.rst +0 -8
- modusa/devtools/docs/source/tools/youtube_downloader.rst +0 -8
- modusa/io/__init__.py +0 -5
- modusa/io/audio_loader.py +0 -184
- modusa/io/base.py +0 -43
- modusa/signals/__init__.py +0 -3
- modusa/signals/audio_signal.py +0 -540
- modusa/signals/base.py +0 -27
- modusa/signals/frequency_domain_signal.py +0 -376
- modusa/signals/spectrogram.py +0 -564
- modusa/signals/time_domain_signal.py +0 -412
- modusa/tools/fourier_tranform.py +0 -24
- modusa-0.2.23.dist-info/RECORD +0 -70
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
modusa/signals/spectrogram.py
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from modusa import excp
|
|
5
|
-
from modusa.decorators import immutable_property, validate_args_type
|
|
6
|
-
from modusa.signals.base import ModusaSignal
|
|
7
|
-
from modusa.tools.math_ops import MathOps
|
|
8
|
-
from typing import Self, Any
|
|
9
|
-
import numpy as np
|
|
10
|
-
import matplotlib.pyplot as plt
|
|
11
|
-
|
|
12
|
-
class Spectrogram(ModusaSignal):
|
|
13
|
-
"""
|
|
14
|
-
A 2D time–frequency representation of a signal.
|
|
15
|
-
|
|
16
|
-
Parameters
|
|
17
|
-
----------
|
|
18
|
-
S : np.ndarray
|
|
19
|
-
2D matrix representing the spectrogram (shape: [n_freqs, n_frames]).
|
|
20
|
-
f : np.ndarray
|
|
21
|
-
Frequency axis corresponding to the rows of `S` (shape: [n_freqs]).
|
|
22
|
-
t : np.ndarray
|
|
23
|
-
Time axis corresponding to the columns of `S` (shape: [n_frames]).
|
|
24
|
-
title : str, optional
|
|
25
|
-
Optional title for the spectrogram (e.g., used in plotting).
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
#--------Meta Information----------
|
|
29
|
-
_name = "Spectrogram"
|
|
30
|
-
_description = ""
|
|
31
|
-
_author_name = "Ankit Anand"
|
|
32
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
33
|
-
_created_at = "2025-07-07"
|
|
34
|
-
#----------------------------------
|
|
35
|
-
|
|
36
|
-
@validate_args_type()
|
|
37
|
-
def __init__(self, S: np.ndarray, f: np.ndarray, frame_rate: float, t0: float = 0.0, title: str | None = None):
|
|
38
|
-
super().__init__()
|
|
39
|
-
|
|
40
|
-
if S.ndim != 2:
|
|
41
|
-
raise excp.InputValueError(f"`S` must have 2 dimensions, got {S.ndim}.")
|
|
42
|
-
if f.ndim != 1:
|
|
43
|
-
raise excp.InputValueError(f"`f` must have 1 dimension, got {f.ndim}.")
|
|
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
|
-
)
|
|
48
|
-
if S.shape[1] == 0:
|
|
49
|
-
raise excp.InputValueError("`S` must have at least one time frame.")
|
|
50
|
-
|
|
51
|
-
self._S = S
|
|
52
|
-
self._f = f
|
|
53
|
-
self._frame_rate = float(frame_rate)
|
|
54
|
-
self._t0 = float(t0)
|
|
55
|
-
self.title = title or self._name
|
|
56
|
-
|
|
57
|
-
#----------------------
|
|
58
|
-
# Properties
|
|
59
|
-
#----------------------
|
|
60
|
-
|
|
61
|
-
@immutable_property("Create a new object instead.")
|
|
62
|
-
def S(self) -> np.ndarray:
|
|
63
|
-
"""Spectrogram matrix (freq × time)."""
|
|
64
|
-
return self._S
|
|
65
|
-
|
|
66
|
-
@immutable_property("Create a new object instead.")
|
|
67
|
-
def f(self) -> np.ndarray:
|
|
68
|
-
"""Frequency axis."""
|
|
69
|
-
return self._f
|
|
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
|
-
|
|
85
|
-
@immutable_property("Create a new object instead.")
|
|
86
|
-
def t(self) -> np.ndarray:
|
|
87
|
-
"""Time axis."""
|
|
88
|
-
n_frames = self._S.shape[1]
|
|
89
|
-
return np.arange(n_frames) / self.frame_rate + self.t0
|
|
90
|
-
|
|
91
|
-
@immutable_property("Read only property.")
|
|
92
|
-
def shape(self) -> np.ndarray:
|
|
93
|
-
"""Shape of the spectrogram (freqs, frames)."""
|
|
94
|
-
return self.S.shape
|
|
95
|
-
|
|
96
|
-
@immutable_property("Read only property.")
|
|
97
|
-
def ndim(self) -> np.ndarray:
|
|
98
|
-
"""Number of dimensions (always 2)."""
|
|
99
|
-
return self.S.ndim
|
|
100
|
-
|
|
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:
|
|
142
|
-
"""Print key information about the spectrogram signal."""
|
|
143
|
-
time_resolution = 1.0 / self.frame_rate
|
|
144
|
-
n_freq_bins = self.S.shape[0]
|
|
145
|
-
|
|
146
|
-
# Estimate NFFT size
|
|
147
|
-
nfft = (n_freq_bins - 1) * 2
|
|
148
|
-
|
|
149
|
-
print("-"*50)
|
|
150
|
-
print(f"{'Title':<20}: {self.title}")
|
|
151
|
-
print(f"{'Kind':<20}: {self._name}")
|
|
152
|
-
print(f"{'Shape':<20}: {self.S.shape} (freq bins × time frames)")
|
|
153
|
-
print(f"{'Frame Rate':<20}: {self.frame_rate} (frames / sec)")
|
|
154
|
-
print(f"{'Time resolution':<20}: {time_resolution:.4f} sec ({time_resolution * 1000:.2f} ms)")
|
|
155
|
-
print(f"{'Freq resolution':<20}: {(self.f[1] - self.f[0]):.2f} Hz")
|
|
156
|
-
print("-"*50)
|
|
157
|
-
|
|
158
|
-
def __getitem__(self, key: tuple[int | slice, int | slice]) -> "Spectrogram | FrequencyDomainSignal | TimeDomainSignal":
|
|
159
|
-
"""
|
|
160
|
-
Enable 2D indexing: signal[f_idx, t_idx]
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
- Spectrogram when both f and t are slices
|
|
164
|
-
- FrequencyDomainSignal when t is int (i.e., fixed time)
|
|
165
|
-
- TimeDomainSignal when f is int (i.e., fixed frequency)
|
|
166
|
-
"""
|
|
167
|
-
from modusa.signals.time_domain_signal import TimeDomainSignal
|
|
168
|
-
from modusa.signals.frequency_domain_signal import FrequencyDomainSignal
|
|
169
|
-
|
|
170
|
-
if isinstance(key, tuple) and len(key) == 2:
|
|
171
|
-
f_key, t_key = key
|
|
172
|
-
|
|
173
|
-
sliced_data = self.S[f_key, t_key]
|
|
174
|
-
sliced_f = self.f[f_key]
|
|
175
|
-
sliced_t = self.t[t_key]
|
|
176
|
-
|
|
177
|
-
# Case 1: Scalar value → return plain numpy scalar
|
|
178
|
-
if np.isscalar(sliced_data):
|
|
179
|
-
return np.array(sliced_data)
|
|
180
|
-
|
|
181
|
-
# Case 2: frequency slice at a single time (→ FrequencyDomainSignal)
|
|
182
|
-
elif isinstance(t_key, int):
|
|
183
|
-
if not isinstance(f_key, int): # already handled scalar case
|
|
184
|
-
sliced_data = np.asarray(sliced_data).flatten()
|
|
185
|
-
sliced_f = np.asarray(sliced_f)
|
|
186
|
-
t0 = float(self.t[t_key])
|
|
187
|
-
return FrequencyDomainSignal(spectrum=sliced_data, f=sliced_f, t0=t0, title=self.title + f" [t = {t0:.2f} sec]")
|
|
188
|
-
|
|
189
|
-
# Case 3: time slice at a single frequency (→ TimeDomainSignal)
|
|
190
|
-
elif isinstance(f_key, int):
|
|
191
|
-
sliced_data = np.asarray(sliced_data).flatten()
|
|
192
|
-
sliced_t = np.asarray(sliced_t)
|
|
193
|
-
sr = 1.0 / np.mean(np.diff(self.t)) # assume uniform time axis
|
|
194
|
-
t0 = float(self.t[0])
|
|
195
|
-
f_val = float(self.f[f_key])
|
|
196
|
-
return TimeDomainSignal(y=sliced_data, sr=sr, t0=t0, title=self.title + f" [f = {f_val:.2f} Hz]")
|
|
197
|
-
|
|
198
|
-
# Case 4: 2D slice → Spectrogram
|
|
199
|
-
else:
|
|
200
|
-
return self.__class__(S=sliced_data, f=sliced_f, frame_rate=self.frame_rate, t0=sliced_t[0], title=self.title)
|
|
201
|
-
|
|
202
|
-
raise TypeError("Expected 2D indexing: signal[f_idx, t_idx]")
|
|
203
|
-
|
|
204
|
-
def crop(
|
|
205
|
-
self,
|
|
206
|
-
f_min: float | None = None,
|
|
207
|
-
f_max: float | None = None,
|
|
208
|
-
t_min: float | None = None,
|
|
209
|
-
t_max: float | None = None
|
|
210
|
-
) -> "Spectrogram":
|
|
211
|
-
"""
|
|
212
|
-
Crop the spectrogram to a rectangular region in frequency-time space.
|
|
213
|
-
|
|
214
|
-
.. code-block:: python
|
|
215
|
-
|
|
216
|
-
cropped = spec.crop(f_min=100, f_max=1000, t_min=5.0, t_max=10.0)
|
|
217
|
-
|
|
218
|
-
Parameters
|
|
219
|
-
----------
|
|
220
|
-
f_min : float or None
|
|
221
|
-
Inclusive lower frequency bound. If None, no lower bound.
|
|
222
|
-
f_max : float or None
|
|
223
|
-
Exclusive upper frequency bound. If None, no upper bound.
|
|
224
|
-
t_min : float or None
|
|
225
|
-
Inclusive lower time bound. If None, no lower bound.
|
|
226
|
-
t_max : float or None
|
|
227
|
-
Exclusive upper time bound. If None, no upper bound.
|
|
228
|
-
|
|
229
|
-
Returns
|
|
230
|
-
-------
|
|
231
|
-
Spectrogram
|
|
232
|
-
Cropped spectrogram.
|
|
233
|
-
"""
|
|
234
|
-
S = self.S
|
|
235
|
-
f = self.f
|
|
236
|
-
t = self.t
|
|
237
|
-
|
|
238
|
-
f_mask = (f >= f_min) if f_min is not None else np.ones_like(f, dtype=bool)
|
|
239
|
-
f_mask &= (f < f_max) if f_max is not None else f_mask
|
|
240
|
-
|
|
241
|
-
t_mask = (t >= t_min) if t_min is not None else np.ones_like(t, dtype=bool)
|
|
242
|
-
t_mask &= (t < t_max) if t_max is not None else t_mask
|
|
243
|
-
|
|
244
|
-
cropped_S = S[np.ix_(f_mask, t_mask)]
|
|
245
|
-
cropped_f = f[f_mask]
|
|
246
|
-
cropped_t = t[t_mask]
|
|
247
|
-
|
|
248
|
-
return self.__class__(S=cropped_S, f=cropped_f, frame_rate=self.frame_rate, t0=cropped_t[0], title=self.title)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def plot(
|
|
252
|
-
self,
|
|
253
|
-
ax: plt.Axes | None = None,
|
|
254
|
-
cmap: str = "gray_r",
|
|
255
|
-
title: str | None = None,
|
|
256
|
-
Mlabel: str | None = None,
|
|
257
|
-
ylabel: str | None = "Frequency (hz)",
|
|
258
|
-
xlabel: str | None = "Time (sec)",
|
|
259
|
-
ylim: tuple[float, float] | None = None,
|
|
260
|
-
xlim: tuple[float, float] | None = None,
|
|
261
|
-
highlight: list[tuple[float, float, float, float]] | None = None,
|
|
262
|
-
vlines: list | None = None,
|
|
263
|
-
hlines: list | None = None,
|
|
264
|
-
origin: str = "lower", # or "lower"
|
|
265
|
-
gamma: int | float | None = None,
|
|
266
|
-
show_colorbar: bool = True,
|
|
267
|
-
cax: plt.Axes | None = None,
|
|
268
|
-
show_grid: bool = True,
|
|
269
|
-
tick_mode: str = "center", # "center" or "edge"
|
|
270
|
-
n_ticks: tuple[int, int] | None = None,
|
|
271
|
-
) -> plt.Figure:
|
|
272
|
-
"""
|
|
273
|
-
Plot the spectrogram using Matplotlib.
|
|
274
|
-
|
|
275
|
-
.. code-block:: python
|
|
276
|
-
|
|
277
|
-
fig = spec.plot(log_compression_factor=10, title="Log-scaled Spectrogram")
|
|
278
|
-
|
|
279
|
-
Parameters
|
|
280
|
-
----------
|
|
281
|
-
log_compression_factor : float or int, optional
|
|
282
|
-
If specified, apply log-compression using log(1 + S * factor).
|
|
283
|
-
ax : matplotlib.axes.Axes, optional
|
|
284
|
-
Axes to draw on. If None, a new figure and axes are created.
|
|
285
|
-
cmap : str, default "gray_r"
|
|
286
|
-
Colormap used for the image.
|
|
287
|
-
title : str, optional
|
|
288
|
-
Title to use for the plot. Defaults to the signal's title.
|
|
289
|
-
Mlabel : str, optional
|
|
290
|
-
Label for the colorbar (e.g., "Magnitude", "dB").
|
|
291
|
-
ylabel : str, optional
|
|
292
|
-
Label for the y-axis. Default is "Frequency (hz)".
|
|
293
|
-
xlabel : str, optional
|
|
294
|
-
Label for the x-axis. Default is "Time (sec)".
|
|
295
|
-
ylim : tuple of float, optional
|
|
296
|
-
Limits for the y-axis (frequency).
|
|
297
|
-
xlim : tuple of float, optional
|
|
298
|
-
Limits for the x-axis (time).
|
|
299
|
-
highlight : list of (x, y, w, h), optional
|
|
300
|
-
Rectangular regions to highlight, specified in data coordinates.
|
|
301
|
-
origin : {"lower", "upper"}, default "lower"
|
|
302
|
-
Origin position for the image (for flipping vertical axis).
|
|
303
|
-
show_colorbar : bool, default True
|
|
304
|
-
Whether to display the colorbar.
|
|
305
|
-
cax : matplotlib.axes.Axes, optional
|
|
306
|
-
Axis to draw the colorbar on. If None, uses default placement.
|
|
307
|
-
show_grid : bool, default True
|
|
308
|
-
Whether to show the major gridlines.
|
|
309
|
-
tick_mode : {"center", "edge"}, default "center"
|
|
310
|
-
Whether to place ticks at bin centers or edges.
|
|
311
|
-
n_ticks : tuple of int, optional
|
|
312
|
-
Number of ticks (y_ticks, x_ticks) to display on each axis.
|
|
313
|
-
|
|
314
|
-
Returns
|
|
315
|
-
-------
|
|
316
|
-
matplotlib.figure.Figure
|
|
317
|
-
The figure object containing the plot.
|
|
318
|
-
"""
|
|
319
|
-
from modusa.tools.plotter import Plotter
|
|
320
|
-
|
|
321
|
-
title = title or self.title
|
|
322
|
-
|
|
323
|
-
fig = Plotter.plot_matrix(
|
|
324
|
-
M=self.S,
|
|
325
|
-
r=self.f,
|
|
326
|
-
c=self.t,
|
|
327
|
-
ax=ax,
|
|
328
|
-
cmap=cmap,
|
|
329
|
-
title=title,
|
|
330
|
-
Mlabel=Mlabel,
|
|
331
|
-
rlabel=ylabel,
|
|
332
|
-
clabel=xlabel,
|
|
333
|
-
rlim=ylim,
|
|
334
|
-
clim=xlim,
|
|
335
|
-
highlight=highlight,
|
|
336
|
-
vlines=vlines,
|
|
337
|
-
hlines=hlines,
|
|
338
|
-
origin=origin,
|
|
339
|
-
gamma=gamma,
|
|
340
|
-
show_colorbar=show_colorbar,
|
|
341
|
-
cax=cax,
|
|
342
|
-
show_grid=show_grid,
|
|
343
|
-
tick_mode=tick_mode,
|
|
344
|
-
n_ticks=n_ticks
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
return fig
|
|
348
|
-
|
|
349
|
-
#----------------------------
|
|
350
|
-
# Math ops
|
|
351
|
-
#----------------------------
|
|
352
|
-
|
|
353
|
-
def __array__(self, dtype=None):
|
|
354
|
-
return np.asarray(self._S, dtype=dtype)
|
|
355
|
-
|
|
356
|
-
def __add__(self, other):
|
|
357
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
360
|
-
|
|
361
|
-
def __radd__(self, other):
|
|
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)
|
|
364
|
-
|
|
365
|
-
def __sub__(self, other):
|
|
366
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
369
|
-
|
|
370
|
-
def __rsub__(self, other):
|
|
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)
|
|
373
|
-
|
|
374
|
-
def __mul__(self, other):
|
|
375
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
378
|
-
|
|
379
|
-
def __rmul__(self, other):
|
|
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)
|
|
382
|
-
|
|
383
|
-
def __truediv__(self, other):
|
|
384
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
387
|
-
|
|
388
|
-
def __rtruediv__(self, other):
|
|
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)
|
|
391
|
-
|
|
392
|
-
def __floordiv__(self, other):
|
|
393
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
396
|
-
|
|
397
|
-
def __rfloordiv__(self, other):
|
|
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)
|
|
400
|
-
|
|
401
|
-
def __pow__(self, other):
|
|
402
|
-
other_data = other.S if isinstance(other, self.__class__) else other
|
|
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)
|
|
405
|
-
|
|
406
|
-
def __rpow__(self, other):
|
|
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)
|
|
409
|
-
|
|
410
|
-
def __abs__(self):
|
|
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
|
-
#--------------------------
|
|
417
|
-
|
|
418
|
-
def sin(self):
|
|
419
|
-
"""Element-wise sine of the spectrogram."""
|
|
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)
|
|
422
|
-
|
|
423
|
-
def cos(self):
|
|
424
|
-
"""Element-wise cosine of the spectrogram."""
|
|
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)
|
|
427
|
-
|
|
428
|
-
def exp(self):
|
|
429
|
-
"""Element-wise exponential of the spectrogram."""
|
|
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)
|
|
432
|
-
|
|
433
|
-
def tanh(self):
|
|
434
|
-
"""Element-wise hyperbolic tangent of the spectrogram."""
|
|
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)
|
|
437
|
-
|
|
438
|
-
def log(self):
|
|
439
|
-
"""Element-wise natural logarithm of the spectrogram."""
|
|
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)
|
|
442
|
-
|
|
443
|
-
def log1p(self):
|
|
444
|
-
"""Element-wise log(1 + M) of the spectrogram."""
|
|
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)
|
|
447
|
-
|
|
448
|
-
def log10(self):
|
|
449
|
-
"""Element-wise base-10 logarithm of the spectrogram."""
|
|
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)
|
|
452
|
-
|
|
453
|
-
def log2(self):
|
|
454
|
-
"""Element-wise base-2 logarithm of the spectrogram."""
|
|
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)
|
|
457
|
-
|
|
458
|
-
#--------------------------
|
|
459
|
-
# Aggregation signal ops
|
|
460
|
-
#--------------------------
|
|
461
|
-
|
|
462
|
-
def mean(self, axis: int | None = None) -> float:
|
|
463
|
-
"""Return the mean of the spectrogram values."""
|
|
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:
|
|
477
|
-
"""Return the standard deviation of the spectrogram values."""
|
|
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:
|
|
491
|
-
"""Return the minimum value in the spectrogram."""
|
|
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:
|
|
505
|
-
"""Return the maximum value in the spectrogram."""
|
|
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:
|
|
519
|
-
"""Return the sum of the spectrogram values."""
|
|
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")
|
|
531
|
-
|
|
532
|
-
#-----------------------------------
|
|
533
|
-
# Repr
|
|
534
|
-
#-----------------------------------
|
|
535
|
-
|
|
536
|
-
def __str__(self):
|
|
537
|
-
cls = self.__class__.__name__
|
|
538
|
-
data = self.S
|
|
539
|
-
|
|
540
|
-
arr_str = np.array2string(
|
|
541
|
-
data,
|
|
542
|
-
separator=", ",
|
|
543
|
-
threshold=50, # limit number of elements shown
|
|
544
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
545
|
-
max_line_width=120, # avoid wrapping
|
|
546
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
|
|
550
|
-
|
|
551
|
-
def __repr__(self):
|
|
552
|
-
cls = self.__class__.__name__
|
|
553
|
-
data = self.S
|
|
554
|
-
|
|
555
|
-
arr_str = np.array2string(
|
|
556
|
-
data,
|
|
557
|
-
separator=", ",
|
|
558
|
-
threshold=50, # limit number of elements shown
|
|
559
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
560
|
-
max_line_width=120, # avoid wrapping
|
|
561
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
562
|
-
)
|
|
563
|
-
|
|
564
|
-
return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
|