modusa 0.4.28__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 (58) 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/synth.py +2 -0
  6. modusa/tools/youtube_downloader.py +1 -4
  7. {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/METADATA +2 -2
  8. modusa-0.4.30.dist-info/RECORD +21 -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.28.dist-info/RECORD +0 -65
  56. {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/WHEEL +0 -0
  57. {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/entry_points.txt +0 -0
  58. {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/licenses/LICENSE.md +0 -0
modusa/models/__init__.py DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from .base import ModusaSignalData, ModusaSignalAxis, ModusaSignal
4
-
5
- from .data import Data
6
- from .s_ax import SAx
7
- from .t_ax import TAx
8
-
9
- from .s1d import S1D
10
- from .tds import TDS
11
- from .audio import Audio
12
-
13
- from .s2d import S2D
14
- from .ftds import FTDS
modusa/models/audio.py DELETED
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from modusa import excp
4
- from modusa.decorators import immutable_property, validate_args_type
5
- from .tds import TDS
6
- from .data import Data
7
- from .t_ax import TAx
8
- from modusa.tools.math_ops import MathOps
9
- from typing import Self, Any
10
- import numpy as np
11
- import matplotlib.pyplot as plt
12
- from pathlib import Path
13
-
14
- class Audio(TDS):
15
- """
16
- Represents a 1D audio signal within modusa framework.
17
-
18
- Note
19
- ----
20
- - Use :class:`~modusa.generators.audio.AudioGen` to instantiate this class.
21
- - Audio must be mono (1D numpy array).
22
-
23
- Parameters
24
- ----------
25
- y: Data
26
- - Data object holding the main audio data.
27
- t: TAx
28
- - Time axis for the audio.
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 = "Audio Signal"
38
- _nickname = "Audio"
39
- _description = ""
40
- _author_name = "Ankit Anand"
41
- _author_email = "ankit0.anand0@gmail.com"
42
- _created_at = "2025-07-04"
43
- #----------------------------------
44
-
45
- @validate_args_type()
46
- def __init__(self, y, t, title = None):
47
-
48
- super().__init__(y=y, t=t, title=title) # Instantiating `TimeDomainSignal` class
49
-
50
- #----------------------------------
51
- # Utility Tools
52
- #----------------------------------
53
-
54
- def play(self, regions = None, title = None):
55
- """
56
- Play the audio signal inside a Jupyter Notebook.
57
-
58
- .. code-block:: python
59
-
60
- import modusa as ms
61
- audio = ms.audio.from_youtube("https://www.youtube.com/watch?v=lIpw9-Y_N0g")
62
- audio.play(regions=[(10, 30, "1")])
63
-
64
- Parameters
65
- ----------
66
- regions : list[tuple[float, float, str], ...] | None
67
- - [(start_time, end_time, 'tag'), ...] pairs in seconds specifying the regions to play.
68
- - e.g. [(1, 10, "Segement 1"), (20, 25, "Segment 2")]
69
- - Default to None for which the entire signal is played.
70
- title : str | None
71
- - Title for the player interface.
72
- - Defaults to None, which means that the signal’s internal title is used.
73
-
74
- Returns
75
- -------
76
- IPython.display.Audio
77
- An interactive audio player widget for Jupyter environments.
78
-
79
- See Also
80
- --------
81
- :class:`~modusa.tools.audio_player.AudioPlayer`
82
- """
83
-
84
- from modusa.tools.audio_player import AudioPlayer
85
-
86
- title = title if title is not None else self.title
87
-
88
- audio_player = AudioPlayer.play(y=self.y.values, sr=self.t.sr, t0=self.t.t0, regions=regions, title=title)
89
-
90
- return audio_player
modusa/models/base.py DELETED
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from abc import ABC, abstractmethod
5
-
6
- class ModusaSignal(ABC):
7
- """
8
- Base class for any signal in the modusa framework.
9
-
10
- Note
11
- ----
12
- - Intended to be subclassed.
13
- """
14
-
15
- #--------Meta Information----------
16
- _name = "Modusa Signal"
17
- _nickname = "MS" # This is to be used in repr/str methods
18
- _description = "Base class for any signal types in the Modusa framework."
19
- _author_name = "Ankit Anand"
20
- _author_email = "ankit0.anand0@gmail.com"
21
- _created_at = "2025-06-23"
22
- #----------------------------------
23
-
24
- def __init__(self):
25
- pass
26
-
27
-
28
- class ModusaSignalAxis(ABC):
29
- """
30
- Base class for any modusa signal axis in the modusa framework.
31
-
32
- Note
33
- ----
34
- - Intended to be subclassed.
35
- """
36
-
37
- #--------Meta Information----------
38
- _name = "Modusa Signal Axis"
39
- _nickname = "MSAx" # This is to be used in repr/str methods
40
- _description = "Base class for any modusa signal axis in the modusa framework."
41
- _author_name = "Ankit Anand"
42
- _author_email = "ankit0.anand0@gmail.com"
43
- _created_at = "2025-07-25"
44
- #----------------------------------
45
-
46
- def __init__(self):
47
- pass
48
-
49
- class ModusaSignalData(ABC):
50
- """
51
- Base class for any modusa signal data in the modusa framework.
52
-
53
- Note
54
- ----
55
- - Intended to be subclassed.
56
- """
57
-
58
- #--------Meta Information----------
59
- _name = "Modusa Signal Data"
60
- _nickname = "MSData" # This is to be used in repr/str methods
61
- _description = "Base class for any modusa signal data in the modusa framework."
62
- _author_name = "Ankit Anand"
63
- _author_email = "ankit0.anand0@gmail.com"
64
- _created_at = "2025-07-27"
65
- #----------------------------------
66
-
67
- def __init__(self):
68
- pass
69
-
70
-
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
- #===============================