modusa 0.2.22__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/decorators.py +4 -4
- modusa/devtools/generate_docs_source.py +96 -0
- modusa/devtools/generate_template.py +13 -13
- modusa/devtools/main.py +4 -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/devtools/templates/test.py +2 -3
- modusa/devtools/templates/{engine.py → tool.py} +3 -8
- modusa/generators/__init__.py +9 -1
- modusa/generators/audio.py +188 -0
- modusa/generators/audio_waveforms.py +22 -13
- 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/main.py +0 -30
- 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 +7 -0
- modusa/tools/audio_converter.py +73 -0
- modusa/tools/audio_loader.py +90 -0
- modusa/tools/audio_player.py +89 -0
- modusa/tools/base.py +43 -0
- modusa/tools/math_ops.py +335 -0
- modusa/tools/plotter.py +351 -0
- modusa/tools/youtube_downloader.py +72 -0
- modusa/utils/excp.py +15 -42
- modusa/utils/np_func_cat.py +44 -0
- modusa/utils/plot.py +142 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
- modusa-0.3.dist-info/RECORD +60 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +0 -3
- modusa/engines/base.py +0 -14
- modusa/io/__init__.py +0 -9
- modusa/io/audio_converter.py +0 -76
- modusa/io/audio_loader.py +0 -214
- modusa/io/audio_player.py +0 -72
- modusa/io/base.py +0 -43
- modusa/io/plotter.py +0 -430
- modusa/io/youtube_downloader.py +0 -139
- modusa/signals/__init__.py +0 -7
- modusa/signals/audio_signal.py +0 -483
- modusa/signals/base.py +0 -34
- modusa/signals/frequency_domain_signal.py +0 -329
- modusa/signals/signal_ops.py +0 -158
- modusa/signals/spectrogram.py +0 -465
- modusa/signals/time_domain_signal.py +0 -309
- modusa-0.2.22.dist-info/RECORD +0 -47
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -1,329 +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 typing import Self, Any
|
|
8
|
-
import numpy as np
|
|
9
|
-
import matplotlib.pyplot as plt
|
|
10
|
-
|
|
11
|
-
class FrequencyDomainSignal(ModusaSignal):
|
|
12
|
-
"""
|
|
13
|
-
Represents a 1D signal in the frequency domain.
|
|
14
|
-
|
|
15
|
-
Note
|
|
16
|
-
----
|
|
17
|
-
- The class is not intended to be instantiated directly
|
|
18
|
-
This class stores the Complex spectrum of a signal
|
|
19
|
-
along with its corresponding frequency axis. It optionally tracks the time
|
|
20
|
-
origin (`t0`) of the spectral slice, which is useful when working with
|
|
21
|
-
time-localized spectral data (e.g., from a spectrogram or short-time Fourier transform).
|
|
22
|
-
|
|
23
|
-
Parameters
|
|
24
|
-
----------
|
|
25
|
-
spectrum : np.ndarray
|
|
26
|
-
The frequency-domain representation of the signal (real or complex-valued).
|
|
27
|
-
f : np.ndarray
|
|
28
|
-
The frequency axis corresponding to the spectrum values. Must match the shape of `spectrum`.
|
|
29
|
-
t0 : float, optional
|
|
30
|
-
The time (in seconds) corresponding to the origin of this spectral slice. Defaults to 0.0.
|
|
31
|
-
title : str, optional
|
|
32
|
-
An optional title for display or plotting purposes.
|
|
33
|
-
"""
|
|
34
|
-
#--------Meta Information----------
|
|
35
|
-
_name = ""
|
|
36
|
-
_description = ""
|
|
37
|
-
_author_name = "Ankit Anand"
|
|
38
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
39
|
-
_created_at = "2025-07-09"
|
|
40
|
-
#----------------------------------
|
|
41
|
-
|
|
42
|
-
@validate_args_type()
|
|
43
|
-
def __init__(self, spectrum: np.ndarray, f: np.ndarray, t0: float | int = 0.0, title: str | None = None):
|
|
44
|
-
super().__init__() # Instantiating `ModusaSignal` class
|
|
45
|
-
|
|
46
|
-
if spectrum.shape != f.shape:
|
|
47
|
-
raise excp.InputValueError(f"`spectrum` and `f` shape must match, got {spectrum.shape} and {f.shape}")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self._spectrum = spectrum
|
|
51
|
-
self._f = f
|
|
52
|
-
self._t0 = float(t0)
|
|
53
|
-
|
|
54
|
-
self.title = title or self._name # This title will be used as plot title by default
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
#----------------------
|
|
58
|
-
# Properties
|
|
59
|
-
#----------------------
|
|
60
|
-
|
|
61
|
-
@immutable_property("Create a new object instead.")
|
|
62
|
-
def spectrum(self) -> np.ndarray:
|
|
63
|
-
"""Complex valued spectrum data."""
|
|
64
|
-
return self._spectrum
|
|
65
|
-
|
|
66
|
-
@immutable_property("Create a new object instead.")
|
|
67
|
-
def f(self) -> np.ndarray:
|
|
68
|
-
"""frequency array of the spectrum."""
|
|
69
|
-
return self._f
|
|
70
|
-
|
|
71
|
-
@immutable_property("Create a new object instead.")
|
|
72
|
-
def t0(self) -> np.ndarray:
|
|
73
|
-
"""Time origin (in seconds) of this spectral slice, e.g., from a spectrogram frame."""
|
|
74
|
-
return self._t0
|
|
75
|
-
|
|
76
|
-
def __len__(self):
|
|
77
|
-
return len(self._y)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#----------------------
|
|
81
|
-
# Tools
|
|
82
|
-
#----------------------
|
|
83
|
-
|
|
84
|
-
def __getitem__(self, key: slice) -> Self:
|
|
85
|
-
sliced_spectrum = self._spectrum[key]
|
|
86
|
-
sliced_f = self._f[key]
|
|
87
|
-
return self.__class__(spectrum=sliced_spectrum, f=sliced_f, t0=self.t0, title=self.title)
|
|
88
|
-
|
|
89
|
-
@validate_args_type()
|
|
90
|
-
def plot(
|
|
91
|
-
self,
|
|
92
|
-
scale_y: tuple[float, float] | None = None,
|
|
93
|
-
ax: plt.Axes | None = None,
|
|
94
|
-
color: str = "b",
|
|
95
|
-
marker: str | None = None,
|
|
96
|
-
linestyle: str | None = None,
|
|
97
|
-
stem: bool | None = False,
|
|
98
|
-
legend_loc: str | None = None,
|
|
99
|
-
title: str | None = None,
|
|
100
|
-
ylabel: str | None = "Amplitude",
|
|
101
|
-
xlabel: str | None = "Freq (Hz)",
|
|
102
|
-
ylim: tuple[float, float] | None = None,
|
|
103
|
-
xlim: tuple[float, float] | None = None,
|
|
104
|
-
highlight: list[tuple[float, float]] | None = None,
|
|
105
|
-
) -> plt.Figure:
|
|
106
|
-
"""
|
|
107
|
-
Plot the frequency-domain signal as a line or stem plot.
|
|
108
|
-
|
|
109
|
-
.. code-block:: python
|
|
110
|
-
|
|
111
|
-
spectrum.plot(stem=True, color="r", title="FFT Frame", xlim=(0, 5000))
|
|
112
|
-
|
|
113
|
-
Parameters
|
|
114
|
-
----------
|
|
115
|
-
scale_y : tuple[float, float], optional
|
|
116
|
-
Range to scale the spectrum values before plotting (min, max).
|
|
117
|
-
ax : matplotlib.axes.Axes, optional
|
|
118
|
-
Axis to plot on. If None, a new figure and axis are created.
|
|
119
|
-
color : str, default="b"
|
|
120
|
-
Color of the line or stem.
|
|
121
|
-
marker : str, optional
|
|
122
|
-
Marker style for points (ignored if stem=True).
|
|
123
|
-
linestyle : str, optional
|
|
124
|
-
Line style for the plot (ignored if stem=True).
|
|
125
|
-
stem : bool, default=False
|
|
126
|
-
Whether to use a stem plot instead of a line plot.
|
|
127
|
-
legend_loc : str, optional
|
|
128
|
-
Legend location (e.g., 'upper right'). If None, no legend is shown.
|
|
129
|
-
title : str, optional
|
|
130
|
-
Title of the plot. Defaults to signal title.
|
|
131
|
-
ylabel : str, default="Amplitude"
|
|
132
|
-
Label for the y-axis.
|
|
133
|
-
xlabel : str, default="Freq (Hz)"
|
|
134
|
-
Label for the x-axis.
|
|
135
|
-
ylim : tuple[float, float], optional
|
|
136
|
-
Limits for the y-axis.
|
|
137
|
-
xlim : tuple[float, float], optional
|
|
138
|
-
Limits for the x-axis.
|
|
139
|
-
highlight : list[tuple[float, float]], optional
|
|
140
|
-
Regions to highlight on the frequency axis as shaded spans.
|
|
141
|
-
|
|
142
|
-
Returns
|
|
143
|
-
-------
|
|
144
|
-
matplotlib.figure.Figure
|
|
145
|
-
The figure containing the plotted signal.
|
|
146
|
-
|
|
147
|
-
Note
|
|
148
|
-
----
|
|
149
|
-
- If `ax` is provided, the plot is drawn on it; otherwise, a new figure is created.
|
|
150
|
-
- `highlight` can be used to emphasize frequency bands (e.g., formants, harmonics).
|
|
151
|
-
- Use `scale_y` to clip or normalize extreme values before plotting.
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
from modusa.io import Plotter
|
|
156
|
-
|
|
157
|
-
title = title or self.title
|
|
158
|
-
|
|
159
|
-
fig: plt.Figure | None = Plotter.plot_signal(y=self.spectrum, x=self.f, scale_y=scale_y, ax=ax, color=color, marker=marker, linestyle=linestyle, stem=stem, legend_loc=legend_loc, title=title, ylabel=ylabel, xlabel=xlabel, ylim=ylim, xlim=xlim, highlight=highlight)
|
|
160
|
-
|
|
161
|
-
return fig
|
|
162
|
-
|
|
163
|
-
#----------------------------
|
|
164
|
-
# Math ops
|
|
165
|
-
#----------------------------
|
|
166
|
-
|
|
167
|
-
def __array__(self, dtype=None):
|
|
168
|
-
return np.asarray(self.spectrum, dtype=dtype)
|
|
169
|
-
|
|
170
|
-
def __add__(self, other):
|
|
171
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
172
|
-
result = np.add(self.spectrum, other_data)
|
|
173
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
174
|
-
|
|
175
|
-
def __radd__(self, other):
|
|
176
|
-
result = np.add(other, self.spectrum)
|
|
177
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
178
|
-
|
|
179
|
-
def __sub__(self, other):
|
|
180
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
181
|
-
result = np.subtract(self.spectrum, other_data)
|
|
182
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
183
|
-
|
|
184
|
-
def __rsub__(self, other):
|
|
185
|
-
result = np.subtract(other, self.spectrum)
|
|
186
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
187
|
-
|
|
188
|
-
def __mul__(self, other):
|
|
189
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
190
|
-
result = np.multiply(self.spectrum, other_data)
|
|
191
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
192
|
-
|
|
193
|
-
def __rmul__(self, other):
|
|
194
|
-
result = np.multiply(other, self.spectrum)
|
|
195
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
196
|
-
|
|
197
|
-
def __truediv__(self, other):
|
|
198
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
199
|
-
result = np.true_divide(self.spectrum, other_data)
|
|
200
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
201
|
-
|
|
202
|
-
def __rtruediv__(self, other):
|
|
203
|
-
result = np.true_divide(other, self.spectrum)
|
|
204
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
205
|
-
|
|
206
|
-
def __floordiv__(self, other):
|
|
207
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
208
|
-
result = np.floor_divide(self.spectrum, other_data)
|
|
209
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
210
|
-
|
|
211
|
-
def __rfloordiv__(self, other):
|
|
212
|
-
result = np.floor_divide(other, self.spectrum)
|
|
213
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
214
|
-
|
|
215
|
-
def __pow__(self, other):
|
|
216
|
-
other_data = other.spectrum if isinstance(other, self.__class__) else other
|
|
217
|
-
result = np.power(self.spectrum, other_data)
|
|
218
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
219
|
-
|
|
220
|
-
def __rpow__(self, other):
|
|
221
|
-
result = np.power(other, self.spectrum)
|
|
222
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
223
|
-
|
|
224
|
-
def __abs__(self):
|
|
225
|
-
result = np.abs(self.spectrum)
|
|
226
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
#--------------------------
|
|
230
|
-
# Other signal ops
|
|
231
|
-
#--------------------------
|
|
232
|
-
def sin(self) -> Self:
|
|
233
|
-
"""Compute the element-wise sine of the signal data."""
|
|
234
|
-
result = np.sin(self.spectrum)
|
|
235
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
236
|
-
|
|
237
|
-
def cos(self) -> Self:
|
|
238
|
-
"""Compute the element-wise cosine of the signal data."""
|
|
239
|
-
result = np.cos(self.spectrum)
|
|
240
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
241
|
-
|
|
242
|
-
def exp(self) -> Self:
|
|
243
|
-
"""Compute the element-wise exponential of the signal data."""
|
|
244
|
-
result = np.exp(self.spectrum)
|
|
245
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
246
|
-
|
|
247
|
-
def tanh(self) -> Self:
|
|
248
|
-
"""Compute the element-wise hyperbolic tangent of the signal data."""
|
|
249
|
-
result = np.tanh(self.spectrum)
|
|
250
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
251
|
-
|
|
252
|
-
def log(self) -> Self:
|
|
253
|
-
"""Compute the element-wise natural logarithm of the signal data."""
|
|
254
|
-
result = np.log(self.spectrum)
|
|
255
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
256
|
-
|
|
257
|
-
def log1p(self) -> Self:
|
|
258
|
-
"""Compute the element-wise natural logarithm of (1 + signal data)."""
|
|
259
|
-
result = np.log1p(self.spectrum)
|
|
260
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
261
|
-
|
|
262
|
-
def log10(self) -> Self:
|
|
263
|
-
"""Compute the element-wise base-10 logarithm of the signal data."""
|
|
264
|
-
result = np.log10(self.spectrum)
|
|
265
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
266
|
-
|
|
267
|
-
def log2(self) -> Self:
|
|
268
|
-
"""Compute the element-wise base-2 logarithm of the signal data."""
|
|
269
|
-
result = np.log2(self.spectrum)
|
|
270
|
-
return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
#--------------------------
|
|
274
|
-
# Aggregation signal ops
|
|
275
|
-
#--------------------------
|
|
276
|
-
def mean(self) -> float:
|
|
277
|
-
"""Compute the mean of the signal data."""
|
|
278
|
-
return float(np.mean(self.spectrum))
|
|
279
|
-
|
|
280
|
-
def std(self) -> float:
|
|
281
|
-
"""Compute the standard deviation of the signal data."""
|
|
282
|
-
return float(np.std(self.spectrum))
|
|
283
|
-
|
|
284
|
-
def min(self) -> float:
|
|
285
|
-
"""Compute the minimum value in the signal data."""
|
|
286
|
-
return float(np.min(self.spectrum))
|
|
287
|
-
|
|
288
|
-
def max(self) -> float:
|
|
289
|
-
"""Compute the maximum value in the signal data."""
|
|
290
|
-
return float(np.max(self.spectrum))
|
|
291
|
-
|
|
292
|
-
def sum(self) -> float:
|
|
293
|
-
"""Compute the sum of the signal data."""
|
|
294
|
-
return float(np.sum(self.spectrum))
|
|
295
|
-
|
|
296
|
-
#-----------------------------------
|
|
297
|
-
# Repr
|
|
298
|
-
#-----------------------------------
|
|
299
|
-
|
|
300
|
-
def __str__(self):
|
|
301
|
-
cls = self.__class__.__name__
|
|
302
|
-
data = self.spectrum
|
|
303
|
-
|
|
304
|
-
arr_str = np.array2string(
|
|
305
|
-
data,
|
|
306
|
-
separator=", ",
|
|
307
|
-
threshold=50, # limit number of elements shown
|
|
308
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
309
|
-
max_line_width=120, # avoid wrapping
|
|
310
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
|
|
314
|
-
|
|
315
|
-
def __repr__(self):
|
|
316
|
-
cls = self.__class__.__name__
|
|
317
|
-
data = self.spectrum
|
|
318
|
-
|
|
319
|
-
arr_str = np.array2string(
|
|
320
|
-
data,
|
|
321
|
-
separator=", ",
|
|
322
|
-
threshold=50, # limit number of elements shown
|
|
323
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
324
|
-
max_line_width=120, # avoid wrapping
|
|
325
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
|
|
329
|
-
|
modusa/signals/signal_ops.py
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
from modusa import excp
|
|
4
|
-
from typing import Any
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
class SignalOps:
|
|
8
|
-
"""
|
|
9
|
-
Performs arithmetic and NumPy-style ops on ModusaSignal instances.
|
|
10
|
-
|
|
11
|
-
Note
|
|
12
|
-
----
|
|
13
|
-
- Shape-changing operations like reshape, transpose, etc. are not yet supported. Use only element-wise or aggregation ops for now.
|
|
14
|
-
- Index alignment must be handled carefully in future extensions.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def _axes_match(a1: tuple[np.ndarray, ...], a2: tuple[np.ndarray, ...]) -> bool:
|
|
18
|
-
"""
|
|
19
|
-
To check if two axes are same.
|
|
20
|
-
"""
|
|
21
|
-
if len(a1) != len(a2):
|
|
22
|
-
return False
|
|
23
|
-
return all(np.allclose(x, y, atol=1e-8) for x, y in zip(a1, a2))
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#----------------------------------
|
|
27
|
-
# To handle basic math operations like
|
|
28
|
-
# +, -, *, **, / ...
|
|
29
|
-
#----------------------------------
|
|
30
|
-
|
|
31
|
-
@staticmethod
|
|
32
|
-
def add(data: np.ndarray, other: np.ndarray) -> np.ndarray:
|
|
33
|
-
return np.add(data, other)
|
|
34
|
-
|
|
35
|
-
@staticmethod
|
|
36
|
-
def subtract(signal: "ModusaSignal", other: Any) -> "ModusaSignal":
|
|
37
|
-
return SignalOps._apply_binary_op(signal, other, np.subtract, "subtract")
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
def multiply(signal: "ModusaSignal", other: Any):
|
|
41
|
-
return SignalOps._apply_binary_op(signal, other, np.multiply, "multiply")
|
|
42
|
-
|
|
43
|
-
@staticmethod
|
|
44
|
-
def divide(signal: "ModusaSignal", other: Any):
|
|
45
|
-
return SignalOps._apply_binary_op(signal, other, np.divide, "divide")
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def power(signal: "ModusaSignal", other: Any):
|
|
49
|
-
return SignalOps._apply_binary_op(signal, other, np.power, "power")
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def floor_divide(signal: "ModusaSignal", other: Any):
|
|
53
|
-
return SignalOps._apply_binary_op(signal, other, np.floor_divide, "floor_divide")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
#----------------------------------
|
|
58
|
-
# To handle numpy aggregator ops
|
|
59
|
-
# mean, sum, ...
|
|
60
|
-
# TODO: Add dimension select option
|
|
61
|
-
#----------------------------------
|
|
62
|
-
@staticmethod
|
|
63
|
-
def mean(signal: "ModusaSignal") -> float:
|
|
64
|
-
"""Return the mean of the signal data."""
|
|
65
|
-
return float(np.mean(signal._data))
|
|
66
|
-
|
|
67
|
-
@staticmethod
|
|
68
|
-
def std(signal: "ModusaSignal") -> float:
|
|
69
|
-
"""Return the standard deviation of the signal data."""
|
|
70
|
-
return float(np.std(signal._data))
|
|
71
|
-
|
|
72
|
-
@staticmethod
|
|
73
|
-
def min(signal: "ModusaSignal") -> float:
|
|
74
|
-
"""Return the minimum value in the signal data."""
|
|
75
|
-
return float(np.min(signal._data))
|
|
76
|
-
|
|
77
|
-
@staticmethod
|
|
78
|
-
def max(signal: "ModusaSignal") -> float:
|
|
79
|
-
"""Return the maximum value in the signal data."""
|
|
80
|
-
return float(np.max(signal._data))
|
|
81
|
-
|
|
82
|
-
@staticmethod
|
|
83
|
-
def sum(signal: "ModusaSignal") -> float:
|
|
84
|
-
"""Return the sum of the signal data."""
|
|
85
|
-
return float(np.sum(signal._data))
|
|
86
|
-
|
|
87
|
-
#----------------------------------
|
|
88
|
-
# To handle numpy ops where the
|
|
89
|
-
# shapes are unaltered
|
|
90
|
-
# sin, cos, exp, log, ...
|
|
91
|
-
#----------------------------------
|
|
92
|
-
|
|
93
|
-
@staticmethod
|
|
94
|
-
def _apply_unary_op(signal: "ModusaSignal", op_func, op_name: str):
|
|
95
|
-
from modusa.signals.base import ModusaSignal # avoid circular import
|
|
96
|
-
|
|
97
|
-
if not isinstance(signal, ModusaSignal):
|
|
98
|
-
raise excp.InputTypeError(f"`signal` must be a ModusaSignal, got {type(signal)}")
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
result = op_func(signal._data)
|
|
102
|
-
except Exception as e:
|
|
103
|
-
raise excp.SignalOpError(f"{op_name} failed: {e}")
|
|
104
|
-
|
|
105
|
-
if not isinstance(result, np.ndarray):
|
|
106
|
-
raise excp.SignalOpError(f"{op_name} did not return a NumPy array, got {type(result)}")
|
|
107
|
-
|
|
108
|
-
if result.shape != signal._data.shape:
|
|
109
|
-
raise excp.SignalOpError(f"{op_name} changed shape: {signal._data.shape} → {result.shape}")
|
|
110
|
-
|
|
111
|
-
return signal.replace(data=result)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@staticmethod
|
|
115
|
-
def sin(signal: "ModusaSignal"):
|
|
116
|
-
return SignalOps._apply_unary_op(signal, np.sin, "sin")
|
|
117
|
-
|
|
118
|
-
@staticmethod
|
|
119
|
-
def cos(signal: "ModusaSignal"):
|
|
120
|
-
return SignalOps._apply_unary_op(signal, np.cos, "cos")
|
|
121
|
-
|
|
122
|
-
@staticmethod
|
|
123
|
-
def exp(signal: "ModusaSignal"):
|
|
124
|
-
return SignalOps._apply_unary_op(signal, np.exp, "exp")
|
|
125
|
-
|
|
126
|
-
@staticmethod
|
|
127
|
-
def tanh(signal: "ModusaSignal"):
|
|
128
|
-
return SignalOps._apply_unary_op(signal, np.tanh, "tanh")
|
|
129
|
-
|
|
130
|
-
@staticmethod
|
|
131
|
-
def log(signal: "ModusaSignal"):
|
|
132
|
-
return SignalOps._apply_unary_op(signal, np.log, "log")
|
|
133
|
-
|
|
134
|
-
@staticmethod
|
|
135
|
-
def log10(signal: "ModusaSignal"):
|
|
136
|
-
return SignalOps._apply_unary_op(signal, np.log10, "log10")
|
|
137
|
-
|
|
138
|
-
@staticmethod
|
|
139
|
-
def log2(signal: "ModusaSignal"):
|
|
140
|
-
return SignalOps._apply_unary_op(signal, np.log2, "log2")
|
|
141
|
-
|
|
142
|
-
@staticmethod
|
|
143
|
-
def log1p(signal: "ModusaSignal"):
|
|
144
|
-
return SignalOps._apply_unary_op(signal, np.log1p, "log1p")
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@staticmethod
|
|
148
|
-
def sqrt(signal: "ModusaSignal"):
|
|
149
|
-
return SignalOps._apply_unary_op(signal, np.sqrt, "sqrt")
|
|
150
|
-
|
|
151
|
-
@staticmethod
|
|
152
|
-
def abs(signal: "ModusaSignal"):
|
|
153
|
-
return SignalOps._apply_unary_op(signal, np.abs, "abs")
|
|
154
|
-
|
|
155
|
-
#------------------------------------
|
|
156
|
-
# TODO: Add shape-changing ops like
|
|
157
|
-
# reshape, transpose, squeeze later
|
|
158
|
-
#------------------------------------
|