modusa 0.4.29__py3-none-any.whl → 0.4.30__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/__init__.py +9 -8
- modusa/tools/__init__.py +7 -2
- modusa/tools/ann_saver.py +30 -0
- modusa/tools/audio_recorder.py +0 -1
- modusa/tools/youtube_downloader.py +1 -4
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/METADATA +2 -2
- modusa-0.4.30.dist-info/RECORD +21 -0
- pyproject.toml +2 -2
- modusa/config.py +0 -18
- modusa/decorators.py +0 -176
- modusa/devtools/generate_docs_source.py +0 -92
- modusa/devtools/generate_template.py +0 -144
- modusa/devtools/list_authors.py +0 -2
- modusa/devtools/list_plugins.py +0 -60
- modusa/devtools/main.py +0 -45
- modusa/devtools/templates/generator.py +0 -24
- modusa/devtools/templates/io.py +0 -24
- modusa/devtools/templates/model.py +0 -47
- modusa/devtools/templates/plugin.py +0 -41
- modusa/devtools/templates/test.py +0 -10
- modusa/devtools/templates/tool.py +0 -24
- modusa/generators/__init__.py +0 -13
- modusa/generators/audio.py +0 -188
- modusa/generators/audio_waveforms.py +0 -236
- modusa/generators/base.py +0 -29
- modusa/generators/ftds.py +0 -298
- modusa/generators/s1d.py +0 -270
- modusa/generators/s2d.py +0 -300
- modusa/generators/s_ax.py +0 -102
- modusa/generators/t_ax.py +0 -64
- modusa/generators/tds.py +0 -267
- modusa/models/__init__.py +0 -14
- modusa/models/audio.py +0 -90
- modusa/models/base.py +0 -70
- modusa/models/data.py +0 -457
- modusa/models/ftds.py +0 -584
- modusa/models/s1d.py +0 -578
- modusa/models/s2d.py +0 -619
- modusa/models/s_ax.py +0 -448
- modusa/models/t_ax.py +0 -335
- modusa/models/tds.py +0 -465
- modusa/plugins/__init__.py +0 -3
- modusa/plugins/base.py +0 -100
- modusa/tools/_plotter_old.py +0 -629
- modusa/tools/audio_saver.py +0 -30
- modusa/tools/base.py +0 -43
- modusa/tools/math_ops.py +0 -335
- modusa/utils/__init__.py +0 -1
- modusa/utils/config.py +0 -25
- modusa/utils/excp.py +0 -49
- modusa/utils/logger.py +0 -18
- modusa/utils/np_func_cat.py +0 -44
- modusa/utils/plot.py +0 -142
- modusa-0.4.29.dist-info/RECORD +0 -65
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/WHEEL +0 -0
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/entry_points.txt +0 -0
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/licenses/LICENSE.md +0 -0
modusa/models/tds.py
DELETED
@@ -1,465 +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 .s1d import S1D
|
7
|
-
from .t_ax import TAx
|
8
|
-
from .data import Data
|
9
|
-
from modusa.tools.math_ops import MathOps
|
10
|
-
from typing import Self, Any, Callable
|
11
|
-
from types import NoneType
|
12
|
-
import numpy as np
|
13
|
-
import matplotlib.pyplot as plt
|
14
|
-
|
15
|
-
class TDS(S1D):
|
16
|
-
"""
|
17
|
-
Space to represent time domain signals.
|
18
|
-
|
19
|
-
Note
|
20
|
-
----
|
21
|
-
- Use :class:`~modusa.generators.tds.TDSGen` to instantiate this class.
|
22
|
-
|
23
|
-
Parameters
|
24
|
-
----------
|
25
|
-
y: Data
|
26
|
-
- Data object holding the main array.
|
27
|
-
t: TAx
|
28
|
-
- Time axis for the signal.
|
29
|
-
title: str
|
30
|
-
- Title for the signal.
|
31
|
-
- Default: None => ''
|
32
|
-
- e.g. "MySignal"
|
33
|
-
- This is used as the title while plotting.
|
34
|
-
"""
|
35
|
-
|
36
|
-
#--------Meta Information----------
|
37
|
-
_name = "Time Domain Signal"
|
38
|
-
_nickname = "signal" # This is to be used in repr/str methods
|
39
|
-
_description = "Space to represent uniform time domain signal."
|
40
|
-
_author_name = "Ankit Anand"
|
41
|
-
_author_email = "ankit0.anand0@gmail.com"
|
42
|
-
_created_at = "2025-07-20"
|
43
|
-
#----------------------------------
|
44
|
-
|
45
|
-
|
46
|
-
def __init__(self, y, t, title = None):
|
47
|
-
|
48
|
-
if not (isinstance(y, Data) and isinstance(t, TAx)):
|
49
|
-
raise TypeError(f"`y` must be `Data` instance and `t` must be `TAx` object, got {type(y)} and {type(x)}")
|
50
|
-
|
51
|
-
assert y.ndim == 1
|
52
|
-
|
53
|
-
super().__init__(y=y, x=t, title=title) # Instantiating `Signal1D` class
|
54
|
-
|
55
|
-
#---------------------------------
|
56
|
-
# Properties (Hidden)
|
57
|
-
#---------------------------------
|
58
|
-
|
59
|
-
@property
|
60
|
-
def y(self) -> Data:
|
61
|
-
return self._y
|
62
|
-
|
63
|
-
@property
|
64
|
-
def t(self) -> TAx:
|
65
|
-
return self.x
|
66
|
-
|
67
|
-
@property
|
68
|
-
def title(self) -> str:
|
69
|
-
return self._title
|
70
|
-
|
71
|
-
@property
|
72
|
-
def shape(self) -> tuple:
|
73
|
-
return self.y.shape
|
74
|
-
|
75
|
-
@property
|
76
|
-
def ndim(self) -> tuple:
|
77
|
-
return self.y.ndim # Should be 1
|
78
|
-
|
79
|
-
@property
|
80
|
-
def size(self) -> int:
|
81
|
-
return self.y.size
|
82
|
-
|
83
|
-
#==================================
|
84
|
-
|
85
|
-
#-------------------------------
|
86
|
-
# NumPy Protocol
|
87
|
-
#-------------------------------
|
88
|
-
|
89
|
-
def __array__(self, dtype=None) -> np.ndarray:
|
90
|
-
return np.asarray(self.y.values, dtype=dtype)
|
91
|
-
|
92
|
-
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
93
|
-
"""
|
94
|
-
Supports NumPy universal functions on the Signal1D object.
|
95
|
-
"""
|
96
|
-
from .data import Data # Ensure this is the same Data class you're using
|
97
|
-
from modusa.utils import np_func_cat as nfc
|
98
|
-
|
99
|
-
raw_inputs = [
|
100
|
-
np.asarray(obj.y) if isinstance(obj, type(self)) else obj
|
101
|
-
for obj in inputs
|
102
|
-
]
|
103
|
-
|
104
|
-
result = getattr(ufunc, method)(*raw_inputs, **kwargs)
|
105
|
-
|
106
|
-
y = Data(values=result, label=None) # label=None or you could copy from self.y.label
|
107
|
-
t = self.t.copy()
|
108
|
-
|
109
|
-
if y.shape != t.shape:
|
110
|
-
raise ValueError(f"`{ufunc.__name__}` caused shape mismatch between data and axis, please create a github issue")
|
111
|
-
|
112
|
-
return self.__class__(y=y, t=t, title=self.title)
|
113
|
-
|
114
|
-
def __array_function__(self, func, types, args, kwargs):
|
115
|
-
"""
|
116
|
-
Additional numpy function support.
|
117
|
-
"""
|
118
|
-
from modusa.utils import np_func_cat as nfc
|
119
|
-
|
120
|
-
if not all(issubclass(t, type(self)) for t in types):
|
121
|
-
return NotImplemented
|
122
|
-
|
123
|
-
# Not supporting concatenate like operations as axis any random axis can't be concatenated
|
124
|
-
if func in nfc.CONCAT_FUNCS:
|
125
|
-
raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
|
126
|
-
|
127
|
-
# Single signal input expected
|
128
|
-
signal = args[0]
|
129
|
-
result: Data = func(signal.y, **kwargs)
|
130
|
-
axis = kwargs.get("axis", None)
|
131
|
-
keepdims = kwargs.get("keepdims", None)
|
132
|
-
|
133
|
-
if func in nfc.REDUCTION_FUNCS:
|
134
|
-
if keepdims is None or keepdims is True: # Default state is True => return 1D signal by wrapping the scalar
|
135
|
-
from .t_ax import TAx
|
136
|
-
dummy_t = TAx(n_points=1, sr=signal.t.sr, t0=0, label=None)
|
137
|
-
return self.__class__(y=result, t=dummy_t, title=signal.title)
|
138
|
-
elif keepdims is False: # Return Data
|
139
|
-
from .data import Data
|
140
|
-
return Data(values=result, label=None)
|
141
|
-
|
142
|
-
elif func in nfc.X_NEEDS_ADJUSTMENT_FUNCS:
|
143
|
-
# You must define logic for adjusting x
|
144
|
-
raise NotImplementedError(f"{func.__name__} requires x-axis adjustment logic.")
|
145
|
-
|
146
|
-
else:
|
147
|
-
raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
|
148
|
-
|
149
|
-
#================================
|
150
|
-
|
151
|
-
#-------------------------------
|
152
|
-
# Indexing
|
153
|
-
#-------------------------------
|
154
|
-
|
155
|
-
def __getitem__(self, key):
|
156
|
-
"""
|
157
|
-
Return a sliced or indexed view of the data.
|
158
|
-
|
159
|
-
Parameters
|
160
|
-
----------
|
161
|
-
key : array-like
|
162
|
-
- Index to apply to the values.
|
163
|
-
|
164
|
-
Returns
|
165
|
-
-------
|
166
|
-
TDS
|
167
|
-
A new TDS object with sliced values and same meta data.
|
168
|
-
"""
|
169
|
-
if not isinstance(key, (int, slice)):
|
170
|
-
raise TypeError(f"Invalid key type: {type(key)}")
|
171
|
-
|
172
|
-
sliced_y = self.y[key]
|
173
|
-
sliced_t = self.t[key]
|
174
|
-
|
175
|
-
if sliced_y.ndim == 0:
|
176
|
-
sliced_y = Data(values=sliced_y.values, label=sliced_y.label, ndim=1)
|
177
|
-
|
178
|
-
return self.__class__(y=sliced_y, t=sliced_t, title=self.title)
|
179
|
-
|
180
|
-
def __setitem__(self, key, value):
|
181
|
-
"""
|
182
|
-
Set values at the specified index.
|
183
|
-
|
184
|
-
Parameters
|
185
|
-
----------
|
186
|
-
key : int | slice | array-like | boolean array | S1D
|
187
|
-
Index to apply to the values.
|
188
|
-
value : int | float | array-like
|
189
|
-
Value(s) to set.
|
190
|
-
"""
|
191
|
-
|
192
|
-
self.y[key] = value # In-place assignment
|
193
|
-
|
194
|
-
#===================================
|
195
|
-
|
196
|
-
|
197
|
-
#-----------------------------------
|
198
|
-
# Utility Methods
|
199
|
-
#-----------------------------------
|
200
|
-
|
201
|
-
def unpack(self):
|
202
|
-
"""
|
203
|
-
Unpacks the object into easy to work
|
204
|
-
with data structures.
|
205
|
-
|
206
|
-
Returns
|
207
|
-
-------
|
208
|
-
(np.ndarray, float, float)
|
209
|
-
- y: Signal data array.
|
210
|
-
- sr: Sampling rate of the signal.
|
211
|
-
- t0: Starting timestamp.
|
212
|
-
"""
|
213
|
-
|
214
|
-
arr = self.y.values
|
215
|
-
sr = self.t.sr
|
216
|
-
t0 = self.t.t0
|
217
|
-
|
218
|
-
return (arr, sr, t0)
|
219
|
-
|
220
|
-
def copy(self) -> Self:
|
221
|
-
"""
|
222
|
-
Returns a new copy of the signal.
|
223
|
-
|
224
|
-
Returns
|
225
|
-
-------
|
226
|
-
Self
|
227
|
-
A new copy of the object.
|
228
|
-
"""
|
229
|
-
copied_y = self.y.copy()
|
230
|
-
copied_x = self.x.copy()
|
231
|
-
title = self.title # Immutable, hence no need to copy
|
232
|
-
|
233
|
-
return self.__class__(y=copied_y, x=copied_x, title=title)
|
234
|
-
|
235
|
-
|
236
|
-
def set_meta_info(self, title = None, y_label = None, t_label = None) -> None:
|
237
|
-
"""
|
238
|
-
Set meta info about the signal.
|
239
|
-
|
240
|
-
Parameters
|
241
|
-
----------
|
242
|
-
title: str
|
243
|
-
- Title for the signal
|
244
|
-
- e.g. "Speedometer"
|
245
|
-
y_label: str
|
246
|
-
- Label for the y-axis.
|
247
|
-
- e.g. "Speeed (m/s)"
|
248
|
-
t_label: str
|
249
|
-
- Label for the time-axis.
|
250
|
-
- e.g. "Distance (m)"
|
251
|
-
"""
|
252
|
-
|
253
|
-
new_title = str(title) if title is not None else self.title
|
254
|
-
new_y_label = str(y_label) if y_label is not None else self.y.label
|
255
|
-
new_t_label = str(t_label) if t_label is not None else self.t.label
|
256
|
-
|
257
|
-
# We create a new copy of the data and axis
|
258
|
-
new_y = self.y.copy().set_meta_info(y_label)
|
259
|
-
new_t = self.t.copy().set_meta_info(t_label)
|
260
|
-
|
261
|
-
return self.__class__(y=new_y, t=new_t, title=title)
|
262
|
-
|
263
|
-
|
264
|
-
def is_same_as(self, other: Self) -> bool:
|
265
|
-
"""
|
266
|
-
Check if two `TDS` instances are equal.
|
267
|
-
"""
|
268
|
-
|
269
|
-
if not isinstance(other, type(self)):
|
270
|
-
return False
|
271
|
-
|
272
|
-
if not self.y.is_same_as(other.y):
|
273
|
-
return False
|
274
|
-
|
275
|
-
if not self.t.is_same_as(other.t):
|
276
|
-
return False
|
277
|
-
|
278
|
-
return True
|
279
|
-
|
280
|
-
def has_same_axis_as(self, other) -> bool:
|
281
|
-
"""
|
282
|
-
Check if two 'TDS' instances have same
|
283
|
-
axis. Many operations need to satify this.
|
284
|
-
"""
|
285
|
-
return self.t.is_same_as(other.t)
|
286
|
-
|
287
|
-
|
288
|
-
def mask(self, condition, set_to=None) -> Self:
|
289
|
-
"""
|
290
|
-
Mask the signal based on condition and
|
291
|
-
the values can be set.
|
292
|
-
|
293
|
-
Parameters
|
294
|
-
----------
|
295
|
-
condition: Callable
|
296
|
-
- Condition function to apply on values of the signal.
|
297
|
-
- E.g. lambda x: x > 10
|
298
|
-
set_to: Number
|
299
|
-
- Number to replace the masked position values.
|
300
|
-
|
301
|
-
Returns
|
302
|
-
-------
|
303
|
-
TDS
|
304
|
-
Masked Signal
|
305
|
-
"""
|
306
|
-
|
307
|
-
mask = condition(self)
|
308
|
-
new_val = set_to
|
309
|
-
|
310
|
-
if set_to is None: # Return the mask as the same signal but with booleans
|
311
|
-
return mask
|
312
|
-
|
313
|
-
else:
|
314
|
-
# We apply the mask and update the signal data
|
315
|
-
new_data = self.y.mask(condition=condition, set_to=new_val)
|
316
|
-
|
317
|
-
# Since we're just updating the data, there is no change in the axis
|
318
|
-
return self.__class__(y=new_data, t=self.t.copy(), title=self.title)
|
319
|
-
#===================================
|
320
|
-
|
321
|
-
#-------------------------------
|
322
|
-
# Tools
|
323
|
-
#-------------------------------
|
324
|
-
|
325
|
-
@validate_args_type()
|
326
|
-
def translate_t(self, n_samples: int):
|
327
|
-
"""
|
328
|
-
Translate the signal along time axis.
|
329
|
-
|
330
|
-
Note
|
331
|
-
----
|
332
|
-
- Negative indexing is allowed but just note that you might end up getting time < 0
|
333
|
-
|
334
|
-
|
335
|
-
.. code-block:: python
|
336
|
-
|
337
|
-
import modusa as ms
|
338
|
-
s1 = ms.tds([1, 2, 4, 4, 5, 3, 2, 1])
|
339
|
-
ms.plot(s1, s1.translate_t(-1), s1.translate_t(3))
|
340
|
-
|
341
|
-
Parameters
|
342
|
-
----------
|
343
|
-
n_samples: int
|
344
|
-
By how many sample you would like to translate the signal.
|
345
|
-
|
346
|
-
Returns
|
347
|
-
-------
|
348
|
-
TDS
|
349
|
-
Translated signal.
|
350
|
-
"""
|
351
|
-
|
352
|
-
translated_t = self.t.translate(n_samples=n_samples)
|
353
|
-
|
354
|
-
return self.__class__(y=self.y.copy(), t=translated_t, title=self.title)
|
355
|
-
|
356
|
-
def pad(self, left=None, right=None) -> Self:
|
357
|
-
"""
|
358
|
-
Pad the signal with array like object from the
|
359
|
-
left or right.
|
360
|
-
|
361
|
-
Parameters
|
362
|
-
----------
|
363
|
-
left: arraylike
|
364
|
-
- What to pad to the left of the signal.
|
365
|
-
- E.g. 1 or [1, 0, 1], np.array([1, 2, 3])
|
366
|
-
right: arraylike
|
367
|
-
- What to pad to the right of the signal.
|
368
|
-
- E.g. 1 or [1, 0, 1], np.array([1, 2, 3])
|
369
|
-
|
370
|
-
Returns
|
371
|
-
-------
|
372
|
-
TDS
|
373
|
-
Padded signal.
|
374
|
-
"""
|
375
|
-
|
376
|
-
if right is None and left is None: # No padding applied
|
377
|
-
return self
|
378
|
-
|
379
|
-
# Pad the data
|
380
|
-
y_padded = self.y.pad(left=left, right=right)
|
381
|
-
|
382
|
-
# Find the new t0
|
383
|
-
if left is not None:
|
384
|
-
if np.ndim(left) == 0: left = np.asarray([left])
|
385
|
-
else: left = np.asarray(left)
|
386
|
-
new_t0 = self.t.t0 - (left.shape[0] / self.t.sr)
|
387
|
-
else:
|
388
|
-
new_t0 = self.t.t0
|
389
|
-
|
390
|
-
t_padded = self.t.__class__(n_points=y_padded.shape[0], sr=self.t.sr, t0=new_t0, label=self.t.label)
|
391
|
-
|
392
|
-
return self.__class__(y=y_padded, t=t_padded, title=self.title)
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
def crop(self, t_min = None, t_max = None, like = None) -> Self:
|
397
|
-
"""
|
398
|
-
Crop the signal to a time range [t_min, t_max].
|
399
|
-
|
400
|
-
.. code-block:: python
|
401
|
-
|
402
|
-
import modusa as ms
|
403
|
-
s1 = ms.tds.random(1000, sr=10)
|
404
|
-
ms.plot(s1, s1.crop(5, 40), s1.crop(20), s1.crop(60, 80))
|
405
|
-
|
406
|
-
|
407
|
-
Parameters
|
408
|
-
----------
|
409
|
-
t_min : float or None
|
410
|
-
Inclusive lower time bound in second (other units). If None, no lower bound.
|
411
|
-
t_max : float or None
|
412
|
-
Exclusive upper time bound in second (other units). If None, no upper bound.
|
413
|
-
like: TDS
|
414
|
-
- A `TDS` object whose start and end time will be used.
|
415
|
-
- If you have a window signal, you can crop the signal to get the correct portion.
|
416
|
-
|
417
|
-
Returns
|
418
|
-
-------
|
419
|
-
TDS
|
420
|
-
Cropped signal.
|
421
|
-
"""
|
422
|
-
|
423
|
-
if like is not None:
|
424
|
-
ref_signal = like
|
425
|
-
assert self.t.sr == ref_signal.t.sr
|
426
|
-
# Set t_min and t_max as per the signal
|
427
|
-
t_min = ref_signal.t.t0
|
428
|
-
t_max = ref_signal.t.end_time
|
429
|
-
|
430
|
-
# We first will find out the time in samples
|
431
|
-
if t_min is not None:
|
432
|
-
t_min_sample = self.t.index_of(t_min)
|
433
|
-
else:
|
434
|
-
t_min_sample = 0
|
435
|
-
|
436
|
-
if t_max is not None:
|
437
|
-
t_max_sample = self.t.index_of(t_max)
|
438
|
-
else:
|
439
|
-
t_max_sample = -1
|
440
|
-
|
441
|
-
return self[t_min_sample: t_max_sample+1]
|
442
|
-
|
443
|
-
#===================================
|
444
|
-
|
445
|
-
#-----------------------------------
|
446
|
-
# Information
|
447
|
-
#-----------------------------------
|
448
|
-
|
449
|
-
def print_info(self) -> None:
|
450
|
-
"""Prints info about the audio."""
|
451
|
-
print("-" * 50)
|
452
|
-
print(f"{'Title'}: {self.title}")
|
453
|
-
print("-" * 50)
|
454
|
-
print(f"{'Type':<20}: {self.__class__.__name__}")
|
455
|
-
print(f"{'Shape':<20}: {self.shape}")
|
456
|
-
print(f"{'Duration':<20}: {self.t.duration:.2f} sec")
|
457
|
-
print(f"{'Sampling Rate':<20}: {self.t.sr} Hz")
|
458
|
-
print(f"{'Sampling Period':<20}: {(1 / self.t.sr * 1000):.2f} ms")
|
459
|
-
|
460
|
-
# Inheritance chain
|
461
|
-
cls_chain = " → ".join(cls.__name__ for cls in reversed(self.__class__.__mro__[:-1]))
|
462
|
-
print(f"{'Inheritance':<20}: {cls_chain}")
|
463
|
-
print("=" * 50)
|
464
|
-
|
465
|
-
#======================================
|
modusa/plugins/__init__.py
DELETED
modusa/plugins/base.py
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
from modusa.decorators import plugin_safety_check
|
4
|
-
from abc import ABC, abstractmethod
|
5
|
-
from typing import Any
|
6
|
-
|
7
|
-
class ModusaPlugin(ABC):
|
8
|
-
"""
|
9
|
-
Base class for any Plugin that follows the modusa framework.
|
10
|
-
"""
|
11
|
-
|
12
|
-
@property
|
13
|
-
@abstractmethod
|
14
|
-
def allowed_input_signal_types(self) -> tuple[type, ...]:
|
15
|
-
"""
|
16
|
-
Define the expected signal types for this plugin.
|
17
|
-
|
18
|
-
Note
|
19
|
-
----
|
20
|
-
- Must be implemented by every `ModusaPlugin` subclass.
|
21
|
-
- Clearly states what signal types are accepted in `.apply()`.
|
22
|
-
- Return a tuple of accepted signal classes.
|
23
|
-
|
24
|
-
Examples
|
25
|
-
--------
|
26
|
-
.. code-block:: python
|
27
|
-
|
28
|
-
# Single type
|
29
|
-
from modusa.signals import Signal1D
|
30
|
-
return (Signal1D, )
|
31
|
-
|
32
|
-
# Multiple types
|
33
|
-
from modusa.signals import Signal1D, Signal2D
|
34
|
-
return (Signal1D, Signal2D)
|
35
|
-
"""
|
36
|
-
pass
|
37
|
-
|
38
|
-
@property
|
39
|
-
@abstractmethod
|
40
|
-
def allowed_output_signal_types(self) -> tuple[type, ...]:
|
41
|
-
"""
|
42
|
-
Defines the allowed return types from the `.apply()` method.
|
43
|
-
|
44
|
-
Note
|
45
|
-
----
|
46
|
-
- Must be implemented by every `ModusaPlugin` subclass.
|
47
|
-
- Clearly declares what return types are valid.
|
48
|
-
- Return a tuple of accepted types (usually signal classes).
|
49
|
-
|
50
|
-
Examples
|
51
|
-
--------
|
52
|
-
.. code-block:: python
|
53
|
-
|
54
|
-
# Single type allowed
|
55
|
-
from modusa.signals import Signal1D
|
56
|
-
return (Signal1D, )
|
57
|
-
|
58
|
-
# Multiple types allowed
|
59
|
-
from modusa.signals import Signal1D, Signal2D
|
60
|
-
return (Signal1D, Signal2D)
|
61
|
-
|
62
|
-
# Return type can also be None (e.g., for plot-only plugins)
|
63
|
-
from modusa.signals import Signal1D, Signal2D
|
64
|
-
return (Signal1D, Signal2D, type(None))
|
65
|
-
"""
|
66
|
-
pass
|
67
|
-
|
68
|
-
@plugin_safety_check()
|
69
|
-
@abstractmethod
|
70
|
-
def apply(self, signal: Any) -> Any:
|
71
|
-
"""
|
72
|
-
Defines the main processing logic of the plugin.
|
73
|
-
|
74
|
-
Note
|
75
|
-
----
|
76
|
-
- Must be implemented by every `ModusaPlugin` subclass.
|
77
|
-
- It is highly advised to wrap it with `@plugin_safety_check()` to:
|
78
|
-
- Validate input and output types.
|
79
|
-
- Enforce plugin contracts for safe execution.
|
80
|
-
|
81
|
-
Warning
|
82
|
-
-------
|
83
|
-
- You should not make this method as `classmethod` or `staticmethod`, this will break plugin safety check.
|
84
|
-
|
85
|
-
Example
|
86
|
-
-------
|
87
|
-
.. code-block:: python
|
88
|
-
|
89
|
-
@plugin_safety_check()
|
90
|
-
def apply(self, signal: "TimeDomainSignal") -> "TimeDomainSignal":
|
91
|
-
from modusa.engines import SomeEngine
|
92
|
-
new_signal: TimeDomainSignal = SomeEngine.run(signal)
|
93
|
-
|
94
|
-
return new_signal
|
95
|
-
"""
|
96
|
-
pass
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|