modusa 0.1.0__py3-none-any.whl → 0.2.0__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 (48) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/decorators.py +5 -5
  3. modusa/devtools/generate_template.py +80 -15
  4. modusa/devtools/main.py +6 -4
  5. modusa/devtools/templates/{engines.py → engine.py} +8 -7
  6. modusa/devtools/templates/{generators.py → generator.py} +8 -10
  7. modusa/devtools/templates/io.py +24 -0
  8. modusa/devtools/templates/{plugins.py → plugin.py} +7 -6
  9. modusa/devtools/templates/signal.py +40 -0
  10. modusa/devtools/templates/test.py +11 -0
  11. modusa/engines/.DS_Store +0 -0
  12. modusa/engines/__init__.py +1 -2
  13. modusa/generators/__init__.py +3 -1
  14. modusa/generators/audio_waveforms.py +227 -0
  15. modusa/generators/base.py +14 -25
  16. modusa/io/__init__.py +9 -0
  17. modusa/io/audio_converter.py +76 -0
  18. modusa/io/audio_loader.py +212 -0
  19. modusa/io/audio_player.py +72 -0
  20. modusa/io/base.py +43 -0
  21. modusa/io/plotter.py +430 -0
  22. modusa/io/youtube_downloader.py +139 -0
  23. modusa/main.py +15 -17
  24. modusa/plugins/__init__.py +1 -7
  25. modusa/signals/__init__.py +4 -6
  26. modusa/signals/audio_signal.py +421 -175
  27. modusa/signals/base.py +11 -271
  28. modusa/signals/frequency_domain_signal.py +329 -0
  29. modusa/signals/signal_ops.py +158 -0
  30. modusa/signals/spectrogram.py +465 -0
  31. modusa/signals/time_domain_signal.py +309 -0
  32. modusa/utils/excp.py +5 -0
  33. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/METADATA +15 -10
  34. modusa-0.2.0.dist-info/RECORD +47 -0
  35. modusa/devtools/templates/signals.py +0 -63
  36. modusa/engines/plot_1dsignal.py +0 -130
  37. modusa/engines/plot_2dmatrix.py +0 -159
  38. modusa/generators/basic_waveform.py +0 -185
  39. modusa/plugins/plot_1dsignal.py +0 -59
  40. modusa/plugins/plot_2dmatrix.py +0 -76
  41. modusa/plugins/plot_time_domain_signal.py +0 -59
  42. modusa/signals/signal1d.py +0 -311
  43. modusa/signals/signal2d.py +0 -226
  44. modusa/signals/uniform_time_domain_signal.py +0 -212
  45. modusa-0.1.0.dist-info/RECORD +0 -41
  46. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/WHEEL +0 -0
  47. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/entry_points.txt +0 -0
  48. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,309 @@
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 TimeDomainSignal(ModusaSignal):
12
+ """
13
+ Initialize a uniformly sampled 1D time-domain signal.
14
+
15
+ This class is specifically designed to hold 1D signals that result
16
+ from slicing a 2D representation like a spectrogram. For example,
17
+ if you have a spectrogram `S` and you perform `S[10, :]`, the result
18
+ is a 1D signal over time, this class provides a clean and consistent
19
+ way to handle such slices.
20
+
21
+ Parameters
22
+ ----------
23
+ y : np.ndarray
24
+ The 1D signal values sampled uniformly over time.
25
+ sr : float
26
+ The sampling rate in Hz (samples per second).
27
+ t0 : float, optional
28
+ The starting time of the signal in seconds (default is 0.0).
29
+ title : str, optional
30
+ An optional title used for labeling or plotting purposes.
31
+ """
32
+
33
+
34
+ #--------Meta Information----------
35
+ _name = "Time Domain Signal"
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, y: np.ndarray, sr: float, t0: float = 0.0, title: str | None = None):
44
+ super().__init__() # Instantiating `ModusaSignal` class
45
+
46
+ self._y = y
47
+ self._sr = sr
48
+ self._t0 = t0
49
+ self.title = title or self._name # This title will be used as plot title by default
50
+
51
+
52
+ #----------------------
53
+ # Properties
54
+ #----------------------
55
+
56
+ @immutable_property("Create a new object instead.")
57
+ def y(self) -> np.ndarray:
58
+ return self._y
59
+
60
+ @immutable_property("Create a new object instead.")
61
+ def sr(self) -> np.ndarray:
62
+ return self._sr
63
+
64
+ @immutable_property("Create a new object instead.")
65
+ def t0(self) -> np.ndarray:
66
+ """"""
67
+ return self._t0
68
+
69
+ @immutable_property("Create a new object instead.")
70
+ def t(self) -> np.ndarray:
71
+ return self.t0 + np.arange(len(self._y)) / self.sr
72
+
73
+ def __len__(self):
74
+ return len(self._y)
75
+
76
+ #----------------------
77
+ # Tools
78
+ #----------------------
79
+
80
+ def __getitem__(self, key: slice) -> Self:
81
+ sliced_y = self._y[key]
82
+ t0_new = self.t[key.start] if key.start is not None else self.t0
83
+ return TimeDomainSignal(y=sliced_y, sr=self.sr, t0=t0_new, title=self.title)
84
+
85
+ @validate_args_type()
86
+ def plot(
87
+ self,
88
+ scale_y: tuple[float, float] | None = None,
89
+ ax: plt.Axes | None = None,
90
+ color: str = "b",
91
+ marker: str | None = None,
92
+ linestyle: str | None = None,
93
+ stem: bool | None = False,
94
+ legend_loc: str | None = None,
95
+ title: str | None = None,
96
+ ylabel: str | None = "Amplitude",
97
+ xlabel: str | None = "Time (sec)",
98
+ ylim: tuple[float, float] | None = None,
99
+ xlim: tuple[float, float] | None = None,
100
+ highlight: list[tuple[float, float]] | None = None,
101
+ ) -> plt.Figure:
102
+ """
103
+ Plot the time-domain signal.
104
+
105
+ .. code-block:: python
106
+
107
+ signal.plot(color='g', marker='o', stem=True)
108
+
109
+ Parameters
110
+ ----------
111
+ scale_y : tuple[float, float], optional
112
+ Min-max values to scale the y-axis data.
113
+ ax : matplotlib.axes.Axes, optional
114
+ Axes to plot on; if None, creates a new figure.
115
+ color : str, default='b'
116
+ Line or stem color.
117
+ marker : str, optional
118
+ Marker style for each data point.
119
+ linestyle : str, optional
120
+ Line style to use if not using stem plot.
121
+ stem : bool, default=False
122
+ Whether to draw a stem plot instead of a line plot.
123
+ legend_loc : str, optional
124
+ If given, adds a legend at the specified location.
125
+
126
+ Returns
127
+ -------
128
+ matplotlib.axes.Axes
129
+ The axes object containing the plot.
130
+
131
+ Note
132
+ ----
133
+ This is useful for visualizing 1D signals obtained from time slices of spectrograms.
134
+ """
135
+
136
+ from modusa.io import Plotter
137
+
138
+ title = title or self.title
139
+
140
+ fig: plt.Figure | None = Plotter.plot_signal(y=self.y, x=self.t, 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)
141
+
142
+ return fig
143
+
144
+ #----------------------------
145
+ # Math ops
146
+ #----------------------------
147
+
148
+ def __array__(self, dtype=None):
149
+ return np.asarray(self.y, dtype=dtype)
150
+
151
+ def __add__(self, other):
152
+ other_data = other.y if isinstance(other, self.__class__) else other
153
+ result = np.add(self.y, other_data)
154
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
155
+
156
+ def __radd__(self, other):
157
+ result = np.add(other, self.y)
158
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
159
+
160
+ def __sub__(self, other):
161
+ other_data = other.y if isinstance(other, self.__class__) else other
162
+ result = np.subtract(self.y, other_data)
163
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
164
+
165
+ def __rsub__(self, other):
166
+ result = np.subtract(other, self.y)
167
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
168
+
169
+ def __mul__(self, other):
170
+ other_data = other.y if isinstance(other, self.__class__) else other
171
+ result = np.multiply(self.y, other_data)
172
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
173
+
174
+ def __rmul__(self, other):
175
+ result = np.multiply(other, self.y)
176
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
177
+
178
+ def __truediv__(self, other):
179
+ other_data = other.y if isinstance(other, self.__class__) else other
180
+ result = np.true_divide(self.y, other_data)
181
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
182
+
183
+ def __rtruediv__(self, other):
184
+ result = np.true_divide(other, self.y)
185
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
186
+
187
+ def __floordiv__(self, other):
188
+ other_data = other.y if isinstance(other, self.__class__) else other
189
+ result = np.floor_divide(self.y, other_data)
190
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
191
+
192
+ def __rfloordiv__(self, other):
193
+ result = np.floor_divide(other, self.y)
194
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
195
+
196
+ def __pow__(self, other):
197
+ other_data = other.y if isinstance(other, self.__class__) else other
198
+ result = np.power(self.y, other_data)
199
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
200
+
201
+ def __rpow__(self, other):
202
+ result = np.power(other, self.y)
203
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
204
+
205
+ def __abs__(self):
206
+ result = np.abs(self.y)
207
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
208
+
209
+
210
+ #--------------------------
211
+ # Other signal ops
212
+ #--------------------------
213
+ def sin(self) -> Self:
214
+ """Compute the element-wise sine of the signal data."""
215
+ result = np.sin(self.y)
216
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
217
+
218
+ def cos(self) -> Self:
219
+ """Compute the element-wise cosine of the signal data."""
220
+ result = np.cos(self.y)
221
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
222
+
223
+ def exp(self) -> Self:
224
+ """Compute the element-wise exponential of the signal data."""
225
+ result = np.exp(self.y)
226
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
227
+
228
+ def tanh(self) -> Self:
229
+ """Compute the element-wise hyperbolic tangent of the signal data."""
230
+ result = np.tanh(self.y)
231
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
232
+
233
+ def log(self) -> Self:
234
+ """Compute the element-wise natural logarithm of the signal data."""
235
+ result = np.log(self.y)
236
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
237
+
238
+ def log1p(self) -> Self:
239
+ """Compute the element-wise natural logarithm of (1 + signal data)."""
240
+ result = np.log1p(self.y)
241
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
242
+
243
+ def log10(self) -> Self:
244
+ """Compute the element-wise base-10 logarithm of the signal data."""
245
+ result = np.log10(self.y)
246
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
247
+
248
+ def log2(self) -> Self:
249
+ """Compute the element-wise base-2 logarithm of the signal data."""
250
+ result = np.log2(self.y)
251
+ return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
252
+
253
+
254
+ #--------------------------
255
+ # Aggregation signal ops
256
+ #--------------------------
257
+ def mean(self) -> float:
258
+ """Compute the mean of the signal data."""
259
+ return float(np.mean(self.y))
260
+
261
+ def std(self) -> float:
262
+ """Compute the standard deviation of the signal data."""
263
+ return float(np.std(self.y))
264
+
265
+ def min(self) -> float:
266
+ """Compute the minimum value in the signal data."""
267
+ return float(np.min(self.y))
268
+
269
+ def max(self) -> float:
270
+ """Compute the maximum value in the signal data."""
271
+ return float(np.max(self.y))
272
+
273
+ def sum(self) -> float:
274
+ """Compute the sum of the signal data."""
275
+ return float(np.sum(self.y))
276
+
277
+ #-----------------------------------
278
+ # Repr
279
+ #-----------------------------------
280
+
281
+ def __str__(self):
282
+ cls = self.__class__.__name__
283
+ data = self.y
284
+
285
+ arr_str = np.array2string(
286
+ data,
287
+ separator=", ",
288
+ threshold=50, # limit number of elements shown
289
+ edgeitems=3, # show first/last 3 rows and columns
290
+ max_line_width=120, # avoid wrapping
291
+ formatter={'float_kind': lambda x: f"{x:.4g}"}
292
+ )
293
+
294
+ return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
295
+
296
+ def __repr__(self):
297
+ cls = self.__class__.__name__
298
+ data = self.y
299
+
300
+ arr_str = np.array2string(
301
+ data,
302
+ separator=", ",
303
+ threshold=50, # limit number of elements shown
304
+ edgeitems=3, # show first/last 3 rows and columns
305
+ max_line_width=120, # avoid wrapping
306
+ formatter={'float_kind': lambda x: f"{x:.4g}"}
307
+ )
308
+
309
+ return f"Signal({arr_str}, shape={data.shape}, kind={cls})"
modusa/utils/excp.py CHANGED
@@ -43,6 +43,11 @@ class PluginInputError(MusaBaseError):
43
43
  class PluginOutputError(MusaBaseError):
44
44
  pass
45
45
 
46
+
47
+
48
+ class SignalOpError(MusaBaseError):
49
+ pass
50
+
46
51
  class AttributeNotFoundError(MusaBaseError):
47
52
  pass
48
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modusa
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A modular signal analysis python library.
5
5
  Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
6
6
  License: MIT
@@ -24,24 +24,29 @@ Requires-Dist: snakeviz>=2.2.2
24
24
  Requires-Dist: line-profiler>=4.2.0
25
25
  Requires-Dist: nbsphinx==0.9.7
26
26
  Requires-Dist: ghp-import>=2.1.0
27
+ Requires-Dist: yt-dlp>=2025.6.30
28
+ Requires-Dist: sphinx-book-theme>=1.1.4
29
+ Requires-Dist: pydata-sphinx-theme>=0.15.4
30
+ Requires-Dist: sphinx-material>=0.0.36
27
31
  Description-Content-Type: text/markdown
28
32
 
29
33
  # modusa
30
34
 
31
- **modusa**: **Mod**ular **U**nified **S**ignal **A**rchitecture* is a flexible, extensible Python framework for building, transforming, and analyzing different signal representations. It is a domain-agnostic core architecture for modern signal processing workflows.
35
+ [**modusa**](https://meluron-toolbox.github.io/modusa/) is a modular framework for audio signal analysis and processing, designed to help audio researchers and developers build DSP chains with minimal code.
32
36
 
33
37
  ---
34
38
 
35
- ## 🔧 Features
39
+ ## Core Components
36
40
 
37
41
  - ⚙️ **modusa Signals**
38
42
  - 🧩 **modusa Plugins**
39
43
  - 📊 **modusa Genetators**
40
- - ♻️ **modusa Engine**
44
+ - 📥 **modusa I/O**
45
+ - ♻️ **modusa Engines**
41
46
 
42
47
  ---
43
48
 
44
- ## 🚀 Installation
49
+ ## Installation
45
50
 
46
51
  > modusa is under active development. You can install the latest version via:
47
52
 
@@ -53,7 +58,7 @@ pdm install
53
58
 
54
59
  ---
55
60
 
56
- ## 🧪 Tests
61
+ ## Tests
57
62
 
58
63
  ```bash
59
64
  pytest tests/
@@ -61,26 +66,26 @@ pytest tests/
61
66
 
62
67
  ---
63
68
 
64
- ## 🧊 Status
69
+ ## Status
65
70
 
66
71
  modusa is in **early alpha**. Expect rapid iteration, breaking changes, and big ideas.
67
72
  If you like the direction, consider ⭐ starring the repo and opening issues or ideas.
68
73
 
69
74
  ---
70
75
 
71
- ## 🧠 About
76
+ ## About
72
77
 
73
78
  **modusa** is developed and maintained by [meluron](https://www.github.com/meluron),
74
79
 
75
80
  ---
76
81
 
77
- ## 📜 License
82
+ ## License
78
83
 
79
84
  MIT License. See `LICENSE` for details.
80
85
 
81
86
  ---
82
87
 
83
- ## 🙌 Contributions
88
+ ## Contributions
84
89
 
85
90
  Pull requests, ideas, and discussions are welcome!
86
91
  No matter which domain you are in, if you work with any signal, we'd love your input.
@@ -0,0 +1,47 @@
1
+ modusa-0.2.0.dist-info/METADATA,sha256=bhV6z427mSpUjdrTjbKjyeVM8BI5IftcpGauCfTBxvE,2117
2
+ modusa-0.2.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ modusa-0.2.0.dist-info/entry_points.txt,sha256=fmKpleVXj6CdaBVL14WoEy6xx7JQCs85jvzwTi3lePM,73
4
+ modusa-0.2.0.dist-info/licenses/LICENSE.md,sha256=JTaXAjx5awk76VArKCx5dUW8vmLEWsL_ZlR7-umaHbA,1078
5
+ modusa/.DS_Store,sha256=Q0eG2RQNYtV4qa5BYj_GznvlEBpNdebi6ZTIR-a7AhU,6148
6
+ modusa/__init__.py,sha256=bfQKVSpAXlKmKvMRIU6WSRQW2qoJsaZsdNJ8E69ghn0,37
7
+ modusa/config.py,sha256=bTqK4t00FZqERVITrxW_q284aDDJAa9aMSfFknfR-oU,280
8
+ modusa/decorators.py,sha256=kGadBm1UzAuWjc4tw9ycOkUm3xP6q-jebGo5yjk_Aqs,5909
9
+ modusa/devtools/generate_template.py,sha256=1WTMIHvBoa42Ow3g2HvYKVNSBxLB6JLGXhs-7G7zi28,5225
10
+ modusa/devtools/list_authors.py,sha256=FWBQKOLznVthvMYMASrx2Gw5lqKHEOccQpBisDZ53Dw,24
11
+ modusa/devtools/list_plugins.py,sha256=g-R5hoaCzHfClY_Sfa788IQ9CAKzQGTfyqmthXDZQqw,1729
12
+ modusa/devtools/main.py,sha256=Me7nkn-oaaxIVuj1aJ5dCq3CB8xODH6i2kqE_pAFmXQ,1836
13
+ modusa/devtools/templates/engine.py,sha256=fmUhvgOpmNtJ5zbYDonyLVjRDzUnVCRzDWY6WeSeiVY,514
14
+ modusa/devtools/templates/generator.py,sha256=tf2k2L9RF5S9iJmqPD5AFETJKvqL5XknqK3sR6n2ceQ,444
15
+ modusa/devtools/templates/io.py,sha256=jcO4vAH8iiw6uvwy2aEbsBeNnQOj5VaU_VrQvhJflRA,383
16
+ modusa/devtools/templates/plugin.py,sha256=HNoN0S63ahk83EPup_15pbfOY5_kFfnLF63U3vWOMp8,863
17
+ modusa/devtools/templates/signal.py,sha256=Jqdq38W6ye2cf499oGXWlpCVH4Nmoo1jNfPYgdVLseA,775
18
+ modusa/devtools/templates/test.py,sha256=OXEeIGW1D8g3a0jLyweNBpwfAQkkClhiIAEI6x0CooU,266
19
+ modusa/engines/.DS_Store,sha256=1lFlJ5EFymdzGAUAaI30vcaaLHt3F1LwpG7xILf9jsM,6148
20
+ modusa/engines/__init__.py,sha256=HtXuC9NWIo6faKTmbalxNvw2CTtiGrsPly7i08GlHVg,54
21
+ modusa/engines/base.py,sha256=gHeGTbvcPhTxwUwt16OLpepMkbG40rv19vs8tN8xqgA,297
22
+ modusa/generators/__init__.py,sha256=vcu5uk1hP637bmPTAiyb0LQ_vkFwgp4r-A1hAHR4zfo,110
23
+ modusa/generators/audio_waveforms.py,sha256=qWg9TUVux8Abxmxa7EjCB_Pt88UFC6bJBqvYRWz8MTg,5868
24
+ modusa/generators/base.py,sha256=QTb-8F19DmoCwLS7Mn9MdugOMZ_22suuTBFrmpyE7LM,934
25
+ modusa/io/__init__.py,sha256=2Jg44klIAjbgLCn4faXXug_4nW-UiMsOH4Z3g0VGxWw,250
26
+ modusa/io/audio_converter.py,sha256=CjsuvdjpHAuwJFjFNCHSzge-i07zW6U-cnb0EIYhMC0,1877
27
+ modusa/io/audio_loader.py,sha256=G2q7wVDGtjWzZ_mlsu2uv5C4TnJynpuqMyBeVLDlw_o,4793
28
+ modusa/io/audio_player.py,sha256=5BrYx1YyTYUybroJSNaZ5n7ABgKWeP4zBCLfhyQYPBE,1626
29
+ modusa/io/base.py,sha256=GOMvpicWWLWXoxm4nLwqq_0hWmXWzYAPLVx-CbthCsQ,1291
30
+ modusa/io/plotter.py,sha256=VViXHuIugAQDcTkSWjbrymrMrQOKVw97jqsd4-kWl_8,11920
31
+ modusa/io/youtube_downloader.py,sha256=7Ah3JCHZTdL47v3m6KthYt80QOm40dDp2qAT2fChgFA,3444
32
+ modusa/main.py,sha256=YeNg_GX3pzP4qNNU7CItCkWt5eOCHhnWLENFcQFlgfc,538
33
+ modusa/plugins/__init__.py,sha256=OB-b_GnUnTkECn1fwxl844UBmiui3mSWIvhBqMZY7Tc,22
34
+ modusa/plugins/base.py,sha256=Bh_1Bja7fOymFsCgwhXDbV6ys3D8muNrPwrfDrG_G_A,2382
35
+ modusa/signals/__init__.py,sha256=TZujCd50gGnO-r27Tv6hrzNl8zBsMIMFwYCeIwszMjc,237
36
+ modusa/signals/audio_signal.py,sha256=WzlMcxWGk3a9X2qkaQFjh3smoUJqkko_8C7WVtYOtnQ,15454
37
+ modusa/signals/base.py,sha256=UdwOTlIHvXjpuCBwE-IcCQBL0F-wuhQpD6aOvI8FvXE,795
38
+ modusa/signals/frequency_domain_signal.py,sha256=wS7MN6y56BDInNgtnkIB3XNsxDS_TNmewqS-A31MJmY,11192
39
+ modusa/signals/signal_ops.py,sha256=tpbZs10NVkT4Y7nsIhqGEP4Y6T8JZ07PiKxF34fYu-I,4685
40
+ modusa/signals/spectrogram.py,sha256=Rbt7AVHuRjRLjHXaoUtaT_uG-CCK6Tc9_P9apH2Oczk,14443
41
+ modusa/signals/time_domain_signal.py,sha256=SOiBVhvsZcctYOGLLtnXXK5IT9wOTO-S4_K0DPWPr-Y,9751
42
+ modusa/utils/.DS_Store,sha256=nLXMwF7QJNuglLI_Gk74F7vl5Dyus2Wd74Mgowijmdo,6148
43
+ modusa/utils/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
44
+ modusa/utils/config.py,sha256=cuGbqbovx5WDQq5rw3hIKcv3CnE5NttjacSOWnP1yu4,576
45
+ modusa/utils/excp.py,sha256=_NLfR1AKU4dOpK-OF0NaChYjaVBCcq1VmSd_dkOexi4,1235
46
+ modusa/utils/logger.py,sha256=K0rsnObeNKCxlNeSnVnJeRhgfmob6riB2uyU7h3dDmA,571
47
+ modusa-0.2.0.dist-info/RECORD,,
@@ -1,63 +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
-
10
- class {class_name}(ModusaSignal):
11
- """
12
-
13
- """
14
-
15
- #--------Meta Information----------
16
- name = ""
17
- description = ""
18
- author_name = "{author_name}"
19
- author_email = "{author_email}"
20
- created_at = "{date_created}"
21
- #----------------------------------
22
-
23
- def __init__(self):
24
- super().__init__() # Instantiating `ModusaSignal` class
25
-
26
-
27
- def _with_data(self, new_data: np.ndarray) -> Self:
28
- """Subclasses must override this to return a copy with new data."""
29
- raise NotImplementedError("Subclasses must implement _with_data")
30
-
31
-
32
- #----------------------
33
- # From methods
34
- #----------------------
35
- @classmethod
36
- def from_array(cls) -> Self:
37
- pass
38
-
39
-
40
- #----------------------
41
- # Setters
42
- #----------------------
43
-
44
-
45
-
46
-
47
- #----------------------
48
- # Properties
49
- #----------------------
50
- @immutable_property("Create a new object instead.")
51
- def data(self) -> np.ndarray:
52
- """"""
53
- pass
54
-
55
- #----------------------
56
- # Plugins Access
57
- #----------------------
58
- def plot(self) -> Any:
59
- """
60
-
61
- """
62
- pass
63
-
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa import excp
5
- from modusa.decorators import validate_args_type
6
- from modusa.engines.base import ModusaEngine
7
- from typing import Any
8
- import numpy as np
9
- import matplotlib.pyplot as plt
10
- from matplotlib.patches import Rectangle
11
-
12
-
13
- class Plot1DSignalEngine(ModusaEngine):
14
- """
15
-
16
- """
17
-
18
- #--------Meta Information----------
19
- name = "Plot 1D Signal"
20
- description = ""
21
- author_name = "Ankit Anand"
22
- author_email = "ankit0.anand0@gmail.com"
23
- created_at = "2025-07-02"
24
- #----------------------------------
25
-
26
- def __init__(self):
27
- super().__init__()
28
-
29
-
30
- @validate_args_type()
31
- def run(
32
- self,
33
- y: np.ndarray,
34
- x: np.ndarray | None,
35
- scale_y: tuple[float, float] | None,
36
- scale_x: tuple[float, float] | None ,
37
- ax: plt.Axes | None,
38
- color: str,
39
- marker: str | None,
40
- linestyle: str | None,
41
- stem: bool | None,
42
- labels: tuple[str, str, str] | None,
43
- legend_loc: str | None,
44
- zoom: tuple | None,
45
- highlight: list[tuple[float, float], ...] | None,
46
- ) -> plt.Figure | None:
47
-
48
-
49
- # Validate the important args and get the signal that needs to be plotted
50
- if y.ndim != 1:
51
- raise excp.InputValueError(f"`y` must be of dimension 1 not {y.ndim}.")
52
- if y.shape[0] < 1:
53
- raise excp.InputValueError(f"`y` must not be empty.")
54
-
55
- if x is None:
56
- x = np.arange(y.shape[0])
57
- elif x.ndim != 1:
58
- raise excp.InputValueError(f"`x` must be of dimension 1 not {x.ndim}.")
59
- elif x.shape[0] < 1:
60
- raise excp.InputValueError(f"`x` must not be empty.")
61
-
62
- if x.shape[0] != y.shape[0]:
63
- raise excp.InputValueError(f"`y` and `x` must be of same shape")
64
-
65
- # Scale the signal if needed
66
- if scale_y is not None:
67
- if len(scale_y) != 2:
68
- raise excp.InputValueError(f"`scale_y` must be tuple of two values (1, 2) => 1y+2")
69
- a, b = scale_y
70
- y = a * y + b
71
-
72
- if scale_x is not None:
73
- if len(scale_x) != 2:
74
- raise excp.InputValueError(f"`scale_x` must be tuple of two values (1, 2) => 1x+2")
75
- a, b = scale_x
76
- x = a * x + b
77
-
78
- # Create a figure
79
- if ax is None:
80
- fig, ax = plt.subplots(figsize=(15, 2))
81
- created_fig = True
82
- else:
83
- fig = ax.get_figure()
84
- created_fig = False
85
-
86
- # Plot the signal with right configurations
87
- plot_label = labels[0] if labels is not None and len(labels) > 0 else None
88
- if stem:
89
- ax.stem(x, y, linefmt=color, markerfmt='o', label=plot_label)
90
- elif marker is not None:
91
- ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, marker=marker, label=plot_label)
92
- else:
93
- ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, label=plot_label)
94
-
95
- # Add legend
96
- if plot_label is not None:
97
- legend_loc = "upper right" if legend_loc is None else legend_loc
98
- ax.legend(loc=legend_loc)
99
-
100
- # Set the labels
101
- if labels is not None:
102
- if len(labels) > 0:
103
- ax.set_title(labels[0])
104
- if len(labels) > 1:
105
- ax.set_ylabel(labels[1])
106
- if len(labels) > 2:
107
- ax.set_xlabel(labels[2])
108
-
109
- # Zoom into a region
110
- if zoom is not None:
111
- ax.set_xlim(zoom)
112
-
113
- # Highlight a list of regions
114
- if highlight is not None:
115
- for highlight_region in highlight:
116
- if len(highlight_region) != 2:
117
- raise excp.InputValueError(f"`highlight should be a list of tuple of 2 values (left, right) => (1, 10.5)")
118
- l, r = highlight_region
119
- ax.add_patch(Rectangle((l, np.min(y)), r - l, np.max(y) - np.min(y), color='red', alpha=0.2, zorder=10))
120
-
121
- # Show/Return the figure as per needed
122
- if created_fig:
123
- fig.tight_layout()
124
- try:
125
- get_ipython
126
- plt.close(fig) # Without this, you will see two plots in the jupyter notebook
127
- return fig
128
- except NameError:
129
- plt.show()
130
- return fig