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
@@ -1,311 +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
- import matplotlib.pyplot as plt
10
-
11
-
12
- class Signal1D(ModusaSignal):
13
- """
14
-
15
- """
16
-
17
- #--------Meta Information----------
18
- name = "1D Signal"
19
- description = ""
20
- author_name = "Ankit Anand"
21
- author_email = "ankit0.anand0@gmail.com"
22
- created_at = "2025-07-02"
23
- #----------------------------------
24
-
25
- @validate_args_type()
26
- def __init__(self, y: np.ndarray, x: np.ndarray | None = None):
27
-
28
- if y.ndim != 1:
29
- raise excp.InputValueError(f"`data` must have 1 dimension not {data.ndim}.")
30
-
31
- if x is None:
32
- x = np.arange(y.shape[0])
33
- else:
34
- if x.ndim != 1:
35
- raise excp.InputValueError(f"`x` must have 1 dimension not {x.ndim}.")
36
- if x.shape[0] != y.shape[0]:
37
- raise excp.InputValueError(f"`x` and `y` must have same shape.")
38
-
39
- super().__init__(data=y, data_idx=x)# Instantiating `ModusaSignal` class
40
-
41
- self._x_unit = "index"
42
- self._y_unit = ""
43
- self._title = self.name
44
- self._y_label = "y"
45
- self._x_label = "x"
46
-
47
- def _with_data(self, new_data: np.ndarray, new_data_idx: np.ndarray) -> Self:
48
- """Subclasses must override this to return a copy with new data."""
49
- new_signal = self.__class__(y=new_data, x=new_data_idx)
50
- new_signal.set_units(y_unit=self.y_unit, x_unit=self.x_unit)
51
- new_signal.set_plot_labels(title=self.title, y_label=self.y_label, x_label=self.x_label)
52
-
53
- return new_signal
54
-
55
- #----------------------
56
- # From methods
57
- #----------------------
58
- @classmethod
59
- @validate_args_type()
60
- def from_array(cls, y: np.ndarray, x: np.ndarray | None = None) -> Self:
61
- """
62
- Loads `Signal1D` instance from numpy array or a python list.
63
-
64
- Note
65
- ----
66
- - If you have python `list`, use `.from_list` instead
67
- """
68
-
69
- signal: Self = cls(y=y, x=x)
70
-
71
- return signal
72
-
73
- @classmethod
74
- @validate_args_type()
75
- def from_list(cls, y: list, x: list | None = None) -> Self:
76
- """
77
- Loads `Signal1D` instance from a python list.
78
- """
79
- y = np.array(y)
80
-
81
- if x is not None:
82
- x = np.array(x)
83
-
84
- signal: Self = cls(y=y, x=x)
85
-
86
- return signal
87
-
88
-
89
- #----------------------
90
- # Setters
91
- #----------------------
92
-
93
- @validate_args_type()
94
- def set_units(self, y_unit: str | None = None, x_unit: str | None = None) -> Self:
95
- """
96
- Set the physical units for the y-axis and x-axis of the signal.
97
-
98
- This method attaches metadata describing the physical units of the signal.
99
- These units are used for display and labeling purposes (e.g., in plots),
100
- but do not affect the underlying signal data.
101
-
102
- Parameters
103
- ----------
104
- y_unit : str or None
105
- Unit of the y-axis (e.g., "V", "Amplitude"). If None, the unit is left unchanged.
106
- x_unit : str or None
107
- Unit of the x-axis (e.g., "s", "Hz"). If None, the unit is left unchanged.
108
-
109
- Returns
110
- -------
111
- Self
112
- The signal instance with updated unit metadata. Supports method chaining.
113
-
114
- Raises
115
- ------
116
- InputTypeError
117
- If `y_unit` or `x_unit` is not a string or None.
118
-
119
- Example
120
- -------
121
- .. code-block:: python
122
-
123
- # Set y-axis to volts and x-axis to seconds
124
- signal.set_units("V", "s")
125
- """
126
-
127
- if y_unit is not None:
128
- self._y_unit = y_unit
129
- if x_unit is not None:
130
- self._x_unit = x_unit
131
-
132
- return self
133
-
134
- @validate_args_type()
135
- def set_plot_labels(
136
- self,
137
- title: str | None = None,
138
- y_label: str | None = None,
139
- x_label: str | None = None
140
- ) -> Self:
141
- """
142
- Set plot-related labels: title, y-axis label, and x-axis label.
143
-
144
- This method is useful for customizing plots generated from the signal,
145
- especially when exporting figures or displaying meaningful metadata.
146
-
147
- Parameters
148
- ----------
149
- title : str or None, optional
150
- The title of the plot (e.g., "Waveform" or "FFT Magnitude").
151
- y_label : str or None, optional
152
- Label for the y-axis (e.g., "Amplitude" or "Power (dB)").
153
- x_label : str or None, optional
154
- Label for the x-axis (e.g., "Time (s)" or "Frequency (Hz)").
155
-
156
- Returns
157
- -------
158
- Self
159
- A modified instance of the signal class.
160
-
161
- Raises
162
- ------
163
- InputTypeError
164
- If any provided argument is not a string or None.
165
-
166
- Examples
167
- --------
168
- .. code-block:: python
169
-
170
- # Set plot title and axis labels for a time-domain signal
171
- signal.set_plot_labels(
172
- title="Time-Domain Signal",
173
- y_label="Amplitude",
174
- x_label="Time (s)"
175
- )
176
- """
177
-
178
- if title is not None:
179
- self._title = title
180
- if y_label is not None:
181
- self._y_label = y_label
182
- if x_label is not None:
183
- self._x_label = x_label
184
-
185
- return self
186
-
187
-
188
-
189
- #----------------------
190
- # Properties
191
- #----------------------
192
- @immutable_property("Create a new object instead.")
193
- def y(self) -> np.ndarray:
194
- return self.data
195
-
196
- @immutable_property("Create a new object instead.")
197
- def x(self) -> np.ndarray:
198
- return self._data_idx
199
-
200
- @immutable_property("Use `.set_units` instead.")
201
- def y_unit(self) -> str:
202
- return self._y_unit
203
-
204
- @immutable_property("Use `.set_units` instead.")
205
- def x_unit(self) -> str:
206
- return self._x_unit
207
-
208
- @immutable_property("Use `.set_labels` instead.")
209
- def title(self) -> str:
210
- return self._title
211
-
212
- @immutable_property("Use `.set_labels` instead.")
213
- def y_label(self) -> str:
214
- return self._y_label
215
-
216
- @immutable_property("Use `.set_labels` instead.")
217
- def x_label(self) -> str:
218
- return self._x_label
219
-
220
- #----------------------
221
- # Additional Properties
222
- #----------------------
223
-
224
- @immutable_property(error_msg="Use `.set_labels` instead.")
225
- def labels(self) -> tuple[str, str, str]:
226
- """Labels in a tuple format appropriate for the plots."""
227
- return (self.title, f"{self.y_label} ({self.y_unit})", f"{self.x_label} ({self.x_unit})")
228
-
229
- #----------------------
230
- # Plugins Access
231
- #----------------------
232
-
233
- def trim(self, region: tuple[float, float]) -> Self:
234
- """
235
- Return a new signal instance trimmed to a specific region of the x-axis.
236
-
237
- This method creates a new signal containing only the portion of the data
238
- where `x` lies within the specified range. It is useful for zooming in on
239
- a region of interest in time, frequency, or any other x-axis domain.
240
-
241
- Parameters
242
- ----------
243
- region : tuple[float, float]
244
- The (start, end) range of the x-axis to retain. Must be in the same
245
- units as the x-axis (e.g., seconds, Hz, samples).
246
-
247
- Returns
248
- -------
249
- Self
250
- A new instance of the signal class, trimmed to the specified region.
251
-
252
- Raises
253
- ------
254
- InputTypeError
255
- If `region` is not a tuple of two floats.
256
-
257
- Examples
258
- --------
259
- .. code-block:: python
260
-
261
- # Trim the signal to the region between x = 0.2 and x = 0.6
262
- trimmed = signal.trim((0.2, 0.6))
263
- """
264
-
265
- from modusa.plugins.trim import Trim1DPlugin
266
- trimmed_signal: Self = Trim1DPlugin(region=region).apply(self)
267
-
268
- return trimmed_signal
269
-
270
-
271
- @validate_args_type()
272
- def plot(
273
- self,
274
- scale_y: tuple[float, float] | None = None,
275
- scale_x: tuple[float, float] | None = None,
276
- ax: plt.Axes | None = None,
277
- color: str = "k",
278
- marker: str | None = None,
279
- linestyle: str | None = None,
280
- stem: bool | None = None,
281
- labels: tuple[str, str, str] | None = None,
282
- legend_loc: str | None = None,
283
- zoom: tuple[float, float] | None = None,
284
- highlight: list[tuple[float, float]] | None = None,
285
- ) -> plt.Figure:
286
- """
287
- Applies `modusa.plugins.Plot1DPlugin` Plugin.
288
- """
289
-
290
- from modusa.plugins import Plot1DSignalPlugin
291
-
292
- labels = labels or self.labels
293
- stem = stem or False
294
-
295
- fig: plt.Figure = Plot1DSignalPlugin().apply(
296
- signal=self,
297
- scale_y=scale_y,
298
- scale_x=scale_x,
299
- ax=ax,
300
- color=color,
301
- marker=marker,
302
- linestyle=linestyle,
303
- stem=stem,
304
- labels=labels,
305
- legend_loc=legend_loc,
306
- zoom=zoom,
307
- highlight=highlight
308
- )
309
-
310
- return fig
311
-
@@ -1,226 +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
- import matplotlib.pyplot as plt
10
-
11
- class Signal2D(ModusaSignal):
12
- """
13
-
14
- """
15
-
16
- #--------Meta Information----------
17
- name = "2D Signal"
18
- description = ""
19
- author_name = "Ankit Anand"
20
- author_email = "ankit0.anand0@gmail.com"
21
- created_at = "2025-07-02"
22
- #----------------------------------
23
-
24
- @validate_args_type()
25
- def __init__(self, M: np.ndarray, r: np.ndarray | None = None, c: np.ndarray | None = None):
26
-
27
- if M.ndim != 2:
28
- raise excp.InputValueError(f"`M` must have 2 dimensions not {M.ndim}.")
29
-
30
- if r is None:
31
- r = np.arange(M.shape[0])
32
- else:
33
- if r.ndim != 1:
34
- raise excp.InputValueError(f"`r` must have 1 dimension not {r.ndim}.")
35
- if r.shape != M.shape[0]:
36
- raise excp.InputValueError(f"`r` must have shape compatible with `M`.")
37
-
38
- if c is None:
39
- c = np.arange(M.shape[0])
40
- else:
41
- if c.ndim != 1:
42
- raise excp.InputValueError(f"`c` must have 1 dimension not {c.ndim}.")
43
- if c.shape != M.shape[1]:
44
- raise excp.InputValueError(f"`c` must have shape compatible with `M`.")
45
-
46
- super().__init__(data=M, data_idx={0: r, 1: c})# Instantiating `ModusaSignal` class
47
-
48
- self._M_unit = ""
49
- self._r_unit = "index"
50
- self._c_unit = "index"
51
-
52
- self._title = self.__class__.name
53
- self._M_label = "Matrix"
54
- self._r_label = "Row"
55
- self._c_label = "Column"
56
-
57
- def _with_data(self, new_data: np.ndarray, new_data_idx: dict) -> Self:
58
- """Subclasses must override this to return a copy with new data."""
59
- new_signal = self.__class__(M=new_data, r=new_data_idx[0], c=new_data_idx[1])
60
- new_signal.set_units(M_unit=self.M_unit, y_unit=self.y_unit, x_unit=self.x_unit)
61
- new_signal.set_plot_labels(title=self.title, M_label=self.M_label, y_label=self.y_label, x_label=self.x_label)
62
-
63
- return new_signal
64
-
65
- #----------------------
66
- # From methods
67
- #----------------------
68
- @classmethod
69
- @validate_args_type()
70
- def from_array(cls, M: np.ndarray, r: np.ndarray | None = None, c: np.ndarray | None = None) -> Self:
71
-
72
- signal: Self = cls(M=M, r=r, c=c)
73
-
74
- return signal
75
-
76
- @classmethod
77
- @validate_args_type()
78
- def from_list(cls, M: list, r: list | None = None, c: list | None = None) -> Self:
79
-
80
- M = np.array(M)
81
- if r is not None:
82
- r = np.array(r)
83
- if c is not None:
84
- c = np.array(c)
85
-
86
- signal: Self = cls(M=M, r=r, c=c)
87
-
88
- return signal
89
-
90
- #----------------------
91
- # Setters
92
- #----------------------
93
-
94
- @validate_args_type()
95
- def set_plot_labels(
96
- self,
97
- title: str | None = None,
98
- M_label: str | None = None,
99
- r_label: str | None = None,
100
- c_label: str | None = None
101
- ) -> Self:
102
-
103
- if title is not None:
104
- self._title = title
105
- if M_label is not None:
106
- self._M_label = M_label
107
- if r_label is not None:
108
- self._r_label = r_label
109
- if c_label is not None:
110
- self._c_label = c_label
111
-
112
- return self
113
-
114
- @validate_args_type()
115
- def set_units(
116
- self,
117
- M_unit: str | None = None,
118
- r_unit: str | None = None,
119
- c_unit: str | None = None
120
- ) -> Self:
121
-
122
- if M_unit is not None:
123
- self._M_unit = M_unit
124
- if r_unit is not None:
125
- self._r_unit = r_unit
126
- if c_unit is not None:
127
- self._c_unit = c_unit
128
-
129
- return self
130
-
131
-
132
-
133
- #----------------------
134
- # Properties
135
- #----------------------
136
-
137
- @immutable_property("Create a new object instead.")
138
- def M(self) -> np.ndarray:
139
- return self.data
140
-
141
- @immutable_property("Create a new object instead.")
142
- def r(self) -> np.ndarray:
143
- return self.data_idx[0]
144
-
145
- @immutable_property("Create a new object instead.")
146
- def c(self) -> np.ndarray:
147
- return self.data_idx[1]
148
-
149
- @immutable_property("Use `.set_units` instead.")
150
- def M_unit(self) -> str:
151
- return self._M_unit
152
-
153
- @immutable_property("Use `.set_units` instead.")
154
- def r_unit(self) -> str:
155
- return self._r_unit
156
-
157
-
158
- @immutable_property("Use `.set_units` instead.")
159
- def c_unit(self) -> str:
160
- return self._c_unit
161
-
162
- @immutable_property("Use `.set_labels` instead.")
163
- def title(self) -> str:
164
- return self._title
165
-
166
-
167
- @immutable_property("Use `.set_labels` instead.")
168
- def M_label(self) -> str:
169
- return self._M_label
170
-
171
-
172
- @immutable_property("Use `.set_labels` instead.")
173
- def r_label(self) -> str:
174
- return self._r_label
175
-
176
-
177
- @immutable_property("Use `.set_labels` instead.")
178
- def c_label(self) -> str:
179
- return self._c_label
180
-
181
-
182
- @property
183
- def labels(self) -> tuple[str, str, str, str]:
184
- """Labels in a format appropriate for the plots."""
185
- return (self.title, f"{self.M_label} ({self.M_unit})", f"{self.r_label} ({self.r_unit})", f"{self.c_label} ({self.c_unit})")
186
-
187
- #----------------------
188
- # Plugins Access
189
- #----------------------
190
- def plot(
191
- self,
192
- log_compression_factor: int | None = None,
193
- ax: plt.Axes | None = None,
194
- labels: tuple[str, str, str, str] | None = None,
195
- zoom: tuple[float, float, float, float] | None = None,
196
- highlight: list[tuple[float, float, float, float]] | None = None,
197
- cmap: str = "gray_r",
198
- origin: str = "upper",
199
- show_colorbar: bool = False,
200
- cax: plt.Axes | None = None,
201
- show_grid: bool = False,
202
- tick_mode: str = "center", # or "edge"
203
- value_range: tuple[float, float] | None = None
204
- ) -> plt.Figure:
205
- """
206
- Applies `modusa.plugins.Plot2DPlugin`.
207
- """
208
- from modusa.plugins import Plot2DMatrixPlugin
209
-
210
- fig = Plot2DMatrixPlugin().apply(
211
- signal=self,
212
- ax=ax,
213
- labels=labels or self.labels,
214
- zoom=zoom,
215
- highlight=highlight,
216
- log_compression_factor=log_compression_factor,
217
- cmap=cmap,
218
- origin=origin,
219
- show_colorbar=show_colorbar,
220
- cax=cax,
221
- show_grid=show_grid,
222
- tick_mode=tick_mode,
223
- value_range=value_range
224
- )
225
- return fig
226
-