modusa 0.4.29__py3-none-any.whl → 0.4.31__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 (58) hide show
  1. modusa/__init__.py +12 -8
  2. modusa/tools/__init__.py +11 -3
  3. modusa/tools/ann_saver.py +30 -0
  4. modusa/tools/audio_recorder.py +0 -1
  5. modusa/tools/audio_stft.py +72 -0
  6. modusa/tools/youtube_downloader.py +1 -4
  7. {modusa-0.4.29.dist-info → modusa-0.4.31.dist-info}/METADATA +2 -2
  8. modusa-0.4.31.dist-info/RECORD +22 -0
  9. pyproject.toml +2 -2
  10. modusa/config.py +0 -18
  11. modusa/decorators.py +0 -176
  12. modusa/devtools/generate_docs_source.py +0 -92
  13. modusa/devtools/generate_template.py +0 -144
  14. modusa/devtools/list_authors.py +0 -2
  15. modusa/devtools/list_plugins.py +0 -60
  16. modusa/devtools/main.py +0 -45
  17. modusa/devtools/templates/generator.py +0 -24
  18. modusa/devtools/templates/io.py +0 -24
  19. modusa/devtools/templates/model.py +0 -47
  20. modusa/devtools/templates/plugin.py +0 -41
  21. modusa/devtools/templates/test.py +0 -10
  22. modusa/devtools/templates/tool.py +0 -24
  23. modusa/generators/__init__.py +0 -13
  24. modusa/generators/audio.py +0 -188
  25. modusa/generators/audio_waveforms.py +0 -236
  26. modusa/generators/base.py +0 -29
  27. modusa/generators/ftds.py +0 -298
  28. modusa/generators/s1d.py +0 -270
  29. modusa/generators/s2d.py +0 -300
  30. modusa/generators/s_ax.py +0 -102
  31. modusa/generators/t_ax.py +0 -64
  32. modusa/generators/tds.py +0 -267
  33. modusa/models/__init__.py +0 -14
  34. modusa/models/audio.py +0 -90
  35. modusa/models/base.py +0 -70
  36. modusa/models/data.py +0 -457
  37. modusa/models/ftds.py +0 -584
  38. modusa/models/s1d.py +0 -578
  39. modusa/models/s2d.py +0 -619
  40. modusa/models/s_ax.py +0 -448
  41. modusa/models/t_ax.py +0 -335
  42. modusa/models/tds.py +0 -465
  43. modusa/plugins/__init__.py +0 -3
  44. modusa/plugins/base.py +0 -100
  45. modusa/tools/_plotter_old.py +0 -629
  46. modusa/tools/audio_saver.py +0 -30
  47. modusa/tools/base.py +0 -43
  48. modusa/tools/math_ops.py +0 -335
  49. modusa/utils/__init__.py +0 -1
  50. modusa/utils/config.py +0 -25
  51. modusa/utils/excp.py +0 -49
  52. modusa/utils/logger.py +0 -18
  53. modusa/utils/np_func_cat.py +0 -44
  54. modusa/utils/plot.py +0 -142
  55. modusa-0.4.29.dist-info/RECORD +0 -65
  56. {modusa-0.4.29.dist-info → modusa-0.4.31.dist-info}/WHEEL +0 -0
  57. {modusa-0.4.29.dist-info → modusa-0.4.31.dist-info}/entry_points.txt +0 -0
  58. {modusa-0.4.29.dist-info → modusa-0.4.31.dist-info}/licenses/LICENSE.md +0 -0
modusa/models/data.py DELETED
@@ -1,457 +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 .base import ModusaSignalData
7
- from typing import Self, Any
8
- import numpy as np
9
-
10
- class Data(ModusaSignalData):
11
- """
12
- Space to represent any modusa signal's data.
13
-
14
- Parameters
15
- ----------
16
- values: array-like
17
- Data values as an array.
18
- label: str
19
- Label for the data.
20
-
21
- Note
22
- ----
23
- - This is only for developers reference.
24
- - As a user of the library, you will never instantiate this class.
25
- - It is used internally and users will be provided APIs to interact with this class if necessary.
26
- """
27
-
28
- #--------Meta Information----------
29
- _name = "Data"
30
- _nickname = "data" # This is to be used in repr/str methods
31
- _description = "Space to represent any signal's data."
32
- _author_name = "Ankit Anand"
33
- _author_email = "ankit0.anand0@gmail.com"
34
- _created_at = "2025-07-27"
35
- #----------------------------------
36
-
37
- def __init__(self, values, label=None):
38
- super().__init__() # Instantiating `ModusaSignalData` class
39
-
40
- values = np.asarray(values)
41
-
42
- if values.ndim > 2:
43
- raise ValueError("We do not currently support, > 2 dim arrays, please create a GitHub issue")
44
-
45
- self._values = values
46
- self._label = label or ""
47
-
48
- #-----------------------------------
49
- # Properties
50
- #-----------------------------------
51
-
52
- @property
53
- def values(self) -> np.ndarray:
54
- return self._values
55
-
56
- @property
57
- def label(self) -> str:
58
- return self._label
59
-
60
- @property
61
- def shape(self) -> tuple:
62
- return self.values.shape
63
-
64
- @property
65
- def ndim(self) -> tuple:
66
- return self.values.ndim
67
-
68
- @property
69
- def size(self) -> int:
70
- return self.values.size
71
-
72
- #===================================
73
-
74
- #-------------------------------
75
- # Utility methods
76
- #-------------------------------
77
-
78
- def is_same_as(self, other) -> bool:
79
- """
80
- Check if two data instances are same.
81
-
82
- Parameters
83
- ----------
84
- other: Data
85
- Another Data object to compare with.
86
-
87
- Returns
88
- -------
89
- bool
90
- True if same ow False
91
-
92
- Note
93
- ----
94
- - We check the shape and all the values.
95
- - We are not checking the labels for now.
96
- """
97
-
98
- arr1 = np.asarray(self)
99
- arr2 = np.asarray(other)
100
-
101
- if isinstance(other, type(self)):
102
- return False
103
- if arr1.shape != arr2.shape:
104
- return False
105
- if not np.allclose(arr1, arr2):
106
- return False
107
-
108
- return True
109
-
110
- def copy(self) -> Self:
111
- """
112
- Return a new copy of data object.
113
-
114
- Returns
115
- -------
116
- Data
117
- A new copy of the data object.
118
- """
119
- copied_values = np.asarray(self).copy()
120
- copied_label = self.label
121
-
122
- return self.__class__(values=copied_values, label=copied_label)
123
-
124
- def set_meta_info(self, label):
125
- """
126
- Set meta info for the data.
127
-
128
- Parameters
129
- ----------
130
- label: str
131
- Label for the data (e.g. "Intensity (dB)").
132
- Returns
133
- -------
134
- Self
135
- A new Self instance with new label.
136
- """
137
-
138
- if label is None:
139
- return self
140
- else:
141
- return self.__class__(values=self.values.copy(), label=label)
142
-
143
- def mask(self, condition, set_to=None) -> Self:
144
- """
145
- Mask the data based on condition and
146
- the values can be set using the `set_to` argument.
147
-
148
- Parameters
149
- ----------
150
- condition: Callable
151
- - Condition function to apply on values of the data.
152
- - E.g. lambda x: x > 10
153
- set_to: Number
154
- - Number to replace the masked position values.
155
-
156
- Returns
157
- -------
158
- Data
159
- Masked Data with either booleans as per the condition or with updated values.
160
- """
161
-
162
- mask = condition(self)
163
- new_value = set_to
164
-
165
- if new_value is None: # Return the mask as the same signal but with booleans
166
- return mask
167
-
168
- else:
169
- # We apply the mask and update the signal data
170
- data_arr = self.values.copy() # We do not want to modify the data inplace
171
- data_arr[mask] = new_value
172
-
173
- updated_data = Data(values=data_arr, label=self.label)
174
-
175
- return updated_data
176
-
177
- def pad(self, left=None, right=None) -> Self:
178
- """
179
- Pad the data from left or right.
180
-
181
- Parameters
182
- ----------
183
- left: arraylike
184
- - What to pad to the left of the signal.
185
- - E.g. 1 or [1, 0, 1], np.array([1, 2, 3])
186
- right: arraylike
187
- - What to pad to the right of the signal.
188
- - E.g. 1 or [1, 0, 1], np.array([1, 2, 3])
189
-
190
- Returns
191
- -------
192
- TDS
193
- Padded signal.
194
- """
195
-
196
- if right is None and left is None: # No padding applied
197
- return self
198
-
199
- values = np.asarray(self).copy()
200
- if self.ndim == 1: # 1D
201
- if isinstance(left, (int, float)): left = [left]
202
- if isinstance(right, (int, float)): right = [right]
203
-
204
- if left is not None:
205
- values = np.concatenate((left, values))
206
- if right is not None:
207
- values = np.concatenate((values, right))
208
-
209
- return self.__class__(values=values, label=self.label)
210
-
211
- elif self.ndim == 2: # 2D
212
- if isinstance(left, (int, float)): left = [left]
213
- if isinstance(right, (int, float)): right = [right]
214
-
215
- if left is not None:
216
- left = np.asarray(left)
217
- if left.ndim != 1:
218
- raise ValueError(f"left must be 1 dimension")
219
- left_cols = np.tile(left, (self.shape[0], 1))
220
- values = np.concatenate((left_cols, values), axis=1)
221
-
222
- if right is not None:
223
- right = np.asarray(right)
224
- if right.ndim != 1:
225
- raise ValueError(f"right must be 1 dimension")
226
- right_cols = np.tile(right, (self.shape[0], 1))
227
- values = np.concatenate((values, right_cols), axis=1)
228
-
229
- return self.__class__(values=values, label=self.label)
230
-
231
-
232
- #================================
233
-
234
-
235
- #-------------------------------
236
- # NumPy Protocol
237
- #-------------------------------
238
- def __array__(self, dtype=None) -> np.ndarray:
239
- return np.asarray(self.values, dtype=dtype)
240
-
241
- def __index__(self):
242
- return int(self.values)
243
-
244
- def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
245
- """
246
- Provides operation support for the universal functions
247
- on the Data object.
248
-
249
- """
250
-
251
- raw_inputs = [x.values if isinstance(x, type(self)) else x for x in inputs]
252
-
253
- # Call the actual ufunc
254
- result = getattr(ufunc, method)(*raw_inputs, **kwargs)
255
-
256
- if isinstance(result, (np.ndarray, np.generic)):
257
- return self.__class__(result, label=self.label)
258
- else:
259
- return result
260
-
261
- def __array_function__(self, func, types, args, kwargs):
262
- """
263
- Additional numpy function support.
264
- """
265
- from modusa.utils import np_func_cat as nfc
266
-
267
- if not all(issubclass(t, type(self)) for t in types):
268
- return NotImplemented
269
-
270
- # Not supporting concatenate like operations as axis any random axis can't be concatenated
271
- if func in nfc.CONCAT_FUNCS:
272
- raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
273
-
274
- # Single signal input expected
275
- data = args[0]
276
- data_arr = np.asarray(data)
277
- if "keepdims" not in kwargs: # We keep keepdim to True by default so that we remain in the same signal space.
278
- kwargs["keepdims"] = True
279
- result_arr = func(data_arr, **kwargs)
280
-
281
- if func in nfc.REDUCTION_FUNCS:
282
- return self.__class__(values=result_arr, label=data.label)
283
-
284
- elif func in nfc.X_NEEDS_ADJUSTMENT_FUNCS:
285
- # You must define logic for adjusting x
286
- raise NotImplementedError(f"{func.__name__} requires x-axis adjustment logic.")
287
-
288
- else:
289
- raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
290
-
291
-
292
- #================================
293
-
294
- #-------------------------------
295
- # Indexing
296
- #-------------------------------
297
-
298
- def __getitem__(self, key):
299
- """
300
- Return a sliced or indexed view of the data.
301
-
302
- Parameters
303
- ----------
304
- key : int | slice | tuple
305
- Index to apply to the values.
306
-
307
- Returns
308
- -------
309
- Data
310
- A new Data object with sliced values and same metadata,
311
- with singleton dimensions restored to match axis alignment.
312
- """
313
- if not isinstance(key, (int, slice, tuple)):
314
- raise TypeError(f"Invalid key type {type(key)}")
315
-
316
- if self.ndim > 2:
317
- raise ValueError("Support for upto 2 dim only")
318
-
319
- sliced_arr = self.values[key]
320
-
321
- # Case 1: 1D array
322
- if self.ndim == 1:
323
- if sliced_arr.ndim == 0: # We have
324
- # We need to make it 1D
325
- sliced_arr = np.expand_dims(sliced_arr, axis=0)
326
-
327
- # Case 2: 2D array
328
- if self.ndim == 2:
329
- if sliced_arr.ndim == 0:
330
- # We need to make it 2D
331
- sliced_arr = np.expand_dims(sliced_arr, axis=0)
332
- sliced_arr = np.expand_dims(sliced_arr, axis=0)
333
- elif sliced_arr.ndim == 1:
334
- if isinstance(key, int):
335
- axis_of_interest = 0
336
- # Assumption is that key is tuple of len 2 with atleast one position being integer, that is our axis of interest
337
- elif isinstance(key, tuple) and len(key) == 2:
338
- if isinstance(key[0], int):
339
- axis_of_interest = 0
340
- elif isinstance(key[1], int):
341
- axis_of_interest = 1
342
- # We need to make it 2D but based on the axis
343
- sliced_arr = np.expand_dims(sliced_arr, axis=axis_of_interest)
344
-
345
- return self.__class__(values=sliced_arr, label=self.label)
346
-
347
- def __setitem__(self, key, value):
348
- """
349
- Set values at the specified index.
350
-
351
- Parameters
352
- ----------
353
- key : int | slice | array-like | boolean array | S1D
354
- Index to apply to the values.
355
- value : int | float | array-like
356
- Value(s) to set.
357
- """
358
- if not isinstance(key, (int, slice, tuple)):
359
- raise TypeError(f"Invalid key type {type(key)}")
360
-
361
- self.values[key] = value # In-place assignment
362
-
363
- #===============================
364
-
365
-
366
- #-------------------------------
367
- # Basic arithmetic operations
368
- #-------------------------------
369
- def __add__(self, other):
370
- return np.add(self, other)
371
-
372
- def __radd__(self, other):
373
- return np.add(other, self)
374
-
375
- def __sub__(self, other):
376
- return np.subtract(self, other)
377
-
378
- def __rsub__(self, other):
379
- return np.subtract(other, self)
380
-
381
- def __mul__(self, other):
382
- return np.multiply(self, other)
383
-
384
- def __rmul__(self, other):
385
- return np.multiply(other, self)
386
-
387
- def __truediv__(self, other):
388
- return np.divide(self, other)
389
-
390
- def __rtruediv__(self, other):
391
- return np.divide(other, self)
392
-
393
- def __floordiv__(self, other):
394
- return np.floor_divide(self, other)
395
-
396
- def __rfloordiv__(self, other):
397
- return np.floor_divide(other, self)
398
-
399
- def __pow__(self, other):
400
- return np.power(self, other)
401
-
402
- def __rpow__(self, other):
403
- return np.power(other, self)
404
-
405
-
406
- #-------------------------------
407
- # Basic comparison operations
408
- #-------------------------------
409
- def __eq__(self, other):
410
- return np.equal(self, other)
411
-
412
- def __ne__(self, other):
413
- return np.not_equal(self, other)
414
-
415
- def __lt__(self, other):
416
- return np.less(self, other)
417
-
418
- def __le__(self, other):
419
- return np.less_equal(self, other)
420
-
421
- def __gt__(self, other):
422
- return np.greater(self, other)
423
-
424
- def __ge__(self, other):
425
- return np.greater_equal(self, other)
426
-
427
- #===============================
428
-
429
- #-------------------------------
430
- # Representation
431
- #-------------------------------
432
-
433
- def __str__(self):
434
-
435
- arr_str = np.array2string(
436
- np.asarray(self),
437
- separator=", ",
438
- threshold=30, # limit number of elements shown
439
- edgeitems=3, # show first/last 3 rows and columns
440
- max_line_width=120, # avoid wrapping
441
- )
442
-
443
- return f"{self._nickname}({arr_str})"
444
-
445
- def __repr__(self):
446
-
447
- arr_str = np.array2string(
448
- np.asarray(self),
449
- separator=", ",
450
- threshold=30, # limit number of elements shown
451
- edgeitems=3, # show first/last 3 rows and columns
452
- max_line_width=120, # avoid wrapping
453
- )
454
-
455
- return f"{self._nickname}({arr_str})"
456
-
457
- #===============================