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.
Files changed (57) hide show
  1. modusa/__init__.py +9 -8
  2. modusa/tools/__init__.py +7 -2
  3. modusa/tools/ann_saver.py +30 -0
  4. modusa/tools/audio_recorder.py +0 -1
  5. modusa/tools/youtube_downloader.py +1 -4
  6. {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/METADATA +2 -2
  7. modusa-0.4.30.dist-info/RECORD +21 -0
  8. pyproject.toml +2 -2
  9. modusa/config.py +0 -18
  10. modusa/decorators.py +0 -176
  11. modusa/devtools/generate_docs_source.py +0 -92
  12. modusa/devtools/generate_template.py +0 -144
  13. modusa/devtools/list_authors.py +0 -2
  14. modusa/devtools/list_plugins.py +0 -60
  15. modusa/devtools/main.py +0 -45
  16. modusa/devtools/templates/generator.py +0 -24
  17. modusa/devtools/templates/io.py +0 -24
  18. modusa/devtools/templates/model.py +0 -47
  19. modusa/devtools/templates/plugin.py +0 -41
  20. modusa/devtools/templates/test.py +0 -10
  21. modusa/devtools/templates/tool.py +0 -24
  22. modusa/generators/__init__.py +0 -13
  23. modusa/generators/audio.py +0 -188
  24. modusa/generators/audio_waveforms.py +0 -236
  25. modusa/generators/base.py +0 -29
  26. modusa/generators/ftds.py +0 -298
  27. modusa/generators/s1d.py +0 -270
  28. modusa/generators/s2d.py +0 -300
  29. modusa/generators/s_ax.py +0 -102
  30. modusa/generators/t_ax.py +0 -64
  31. modusa/generators/tds.py +0 -267
  32. modusa/models/__init__.py +0 -14
  33. modusa/models/audio.py +0 -90
  34. modusa/models/base.py +0 -70
  35. modusa/models/data.py +0 -457
  36. modusa/models/ftds.py +0 -584
  37. modusa/models/s1d.py +0 -578
  38. modusa/models/s2d.py +0 -619
  39. modusa/models/s_ax.py +0 -448
  40. modusa/models/t_ax.py +0 -335
  41. modusa/models/tds.py +0 -465
  42. modusa/plugins/__init__.py +0 -3
  43. modusa/plugins/base.py +0 -100
  44. modusa/tools/_plotter_old.py +0 -629
  45. modusa/tools/audio_saver.py +0 -30
  46. modusa/tools/base.py +0 -43
  47. modusa/tools/math_ops.py +0 -335
  48. modusa/utils/__init__.py +0 -1
  49. modusa/utils/config.py +0 -25
  50. modusa/utils/excp.py +0 -49
  51. modusa/utils/logger.py +0 -18
  52. modusa/utils/np_func_cat.py +0 -44
  53. modusa/utils/plot.py +0 -142
  54. modusa-0.4.29.dist-info/RECORD +0 -65
  55. {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/WHEEL +0 -0
  56. {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/entry_points.txt +0 -0
  57. {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
- #======================================
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from .base import ModusaPlugin
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
-