modusa 0.2.22__py3-none-any.whl → 0.3__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 (70) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/__init__.py +8 -1
  3. modusa/decorators.py +4 -4
  4. modusa/devtools/generate_docs_source.py +96 -0
  5. modusa/devtools/generate_template.py +13 -13
  6. modusa/devtools/main.py +4 -3
  7. modusa/devtools/templates/generator.py +1 -1
  8. modusa/devtools/templates/io.py +1 -1
  9. modusa/devtools/templates/{signal.py → model.py} +18 -11
  10. modusa/devtools/templates/plugin.py +1 -1
  11. modusa/devtools/templates/test.py +2 -3
  12. modusa/devtools/templates/{engine.py → tool.py} +3 -8
  13. modusa/generators/__init__.py +9 -1
  14. modusa/generators/audio.py +188 -0
  15. modusa/generators/audio_waveforms.py +22 -13
  16. modusa/generators/base.py +1 -1
  17. modusa/generators/ftds.py +298 -0
  18. modusa/generators/s1d.py +270 -0
  19. modusa/generators/s2d.py +300 -0
  20. modusa/generators/s_ax.py +102 -0
  21. modusa/generators/t_ax.py +64 -0
  22. modusa/generators/tds.py +267 -0
  23. modusa/main.py +0 -30
  24. modusa/models/__init__.py +14 -0
  25. modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
  26. modusa/models/audio.py +90 -0
  27. modusa/models/base.py +70 -0
  28. modusa/models/data.py +457 -0
  29. modusa/models/ftds.py +584 -0
  30. modusa/models/s1d.py +578 -0
  31. modusa/models/s2d.py +619 -0
  32. modusa/models/s_ax.py +448 -0
  33. modusa/models/t_ax.py +335 -0
  34. modusa/models/tds.py +465 -0
  35. modusa/plugins/__init__.py +3 -1
  36. modusa/tmp.py +98 -0
  37. modusa/tools/__init__.py +7 -0
  38. modusa/tools/audio_converter.py +73 -0
  39. modusa/tools/audio_loader.py +90 -0
  40. modusa/tools/audio_player.py +89 -0
  41. modusa/tools/base.py +43 -0
  42. modusa/tools/math_ops.py +335 -0
  43. modusa/tools/plotter.py +351 -0
  44. modusa/tools/youtube_downloader.py +72 -0
  45. modusa/utils/excp.py +15 -42
  46. modusa/utils/np_func_cat.py +44 -0
  47. modusa/utils/plot.py +142 -0
  48. {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
  49. modusa-0.3.dist-info/RECORD +60 -0
  50. modusa/engines/.DS_Store +0 -0
  51. modusa/engines/__init__.py +0 -3
  52. modusa/engines/base.py +0 -14
  53. modusa/io/__init__.py +0 -9
  54. modusa/io/audio_converter.py +0 -76
  55. modusa/io/audio_loader.py +0 -214
  56. modusa/io/audio_player.py +0 -72
  57. modusa/io/base.py +0 -43
  58. modusa/io/plotter.py +0 -430
  59. modusa/io/youtube_downloader.py +0 -139
  60. modusa/signals/__init__.py +0 -7
  61. modusa/signals/audio_signal.py +0 -483
  62. modusa/signals/base.py +0 -34
  63. modusa/signals/frequency_domain_signal.py +0 -329
  64. modusa/signals/signal_ops.py +0 -158
  65. modusa/signals/spectrogram.py +0 -465
  66. modusa/signals/time_domain_signal.py +0 -309
  67. modusa-0.2.22.dist-info/RECORD +0 -47
  68. {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
  69. {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
  70. {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
modusa/io/plotter.py DELETED
@@ -1,430 +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.io import ModusaIO
7
- import numpy as np
8
- import matplotlib.pyplot as plt
9
- from matplotlib.patches import Rectangle
10
- from matplotlib.ticker import MaxNLocator
11
- import warnings
12
-
13
- warnings.filterwarnings("ignore", message="Glyph .* missing from font.*") # To supress any font related warnings, TODO: Add support to Devnagri font
14
-
15
-
16
- class Plotter(ModusaIO):
17
- """
18
- Plots different kind of signals using `matplotlib`.
19
-
20
- Note
21
- ----
22
- - The class has `plot_` methods to plot different types of signals (1D, 2D).
23
-
24
- """
25
-
26
- #--------Meta Information----------
27
- _name = ""
28
- _description = ""
29
- _author_name = "Ankit Anand"
30
- _author_email = "ankit0.anand0@gmail.com"
31
- _created_at = "2025-07-06"
32
- #----------------------------------
33
-
34
- @staticmethod
35
- def plot_signal(
36
- y: np.ndarray,
37
- x: np.ndarray | None,
38
- scale_y: tuple[float, float] | None = None,
39
- ax: plt.Axes | None = None,
40
- color: str = "k",
41
- marker: str | None = None,
42
- linestyle: str | None = None,
43
- stem: bool = False,
44
- labels: tuple[str, str, str] | None = None,
45
- legend_loc: str | None = None,
46
- title: str | None = None,
47
- ylabel: str | None = None,
48
- xlabel: str | None = None,
49
- ylim: tuple[float, float] | None = None,
50
- xlim: tuple[float, float] | None = None,
51
- highlight: list[tuple[float, float], ...] | None = None,
52
- ) -> plt.Figure | None:
53
- """
54
- Plots 1D signal using `matplotlib` with various settings passed through the
55
- arguments.
56
-
57
- .. code-block:: python
58
-
59
- from modusa.io import Plotter
60
- import numpy as np
61
-
62
- # Generate a sample sine wave
63
- x = np.linspace(0, 2 * np.pi, 100)
64
- y = np.sin(x)
65
-
66
- # Plot the signal
67
- fig = Plotter.plot_signal(
68
- y=y,
69
- x=x,
70
- scale_y=None,
71
- ax=None,
72
- color="blue",
73
- marker=None,
74
- linestyle="-",
75
- stem=False,
76
- labels=("Time", "Amplitude", "Sine Wave"),
77
- legend_loc="upper right",
78
- zoom=None,
79
- highlight=[(2, 4)]
80
- )
81
-
82
-
83
- Parameters
84
- ----------
85
- y: np.ndarray
86
- The signal values to plot on the y-axis.
87
- x: np.ndarray | None
88
- The x-axis values. If None, indices of `y` are used.
89
- scale_y: tuple[float, float] | None
90
- Linear scaling for `y` values, (a, b) => ay+b
91
- ax: plt.Axes | None
92
- matplotlib Axes object to draw on. If None, a new figure and axis are created. Return type depends on parameter value.
93
- color: str
94
- Color of the plotted line or markers. (e.g. "k")
95
- marker: str | None
96
- marker style for the plot (e.g., 'o', 'x'). If None, no marker is used.
97
- linestyle: str | None
98
- Line style for the plot (e.g., '-', '--'). If None, no line is drawn.
99
- stem: bool
100
- If True, plots a stem plot.
101
- labels: tuple[str, str, str] | None
102
- Tuple containing (title, xlabel, ylabel). If None, no labels are set.
103
- legend_loc: str | None
104
- Location string for legend placement (e.g., 'upper right'). If None, no legend is shown.
105
- zoom: tuple | None
106
- Tuple specifying x-axis limits for zoom as (start, end). If None, full x-range is shown.
107
- highlight: list[tuple[float, float], ...] | None
108
- List of (start, end) tuples to highlight regions on the plot. e.g. [(1, 2.5), (6, 10)]
109
-
110
- Returns
111
- -------
112
- plt.Figure | None
113
- Figure if `ax` is None else None.
114
-
115
-
116
- """
117
-
118
- # Validate the important args and get the signal that needs to be plotted
119
- if y.ndim != 1:
120
- raise excp.InputValueError(f"`y` must be of dimension 1 not {y.ndim}.")
121
- if y.shape[0] < 1:
122
- raise excp.InputValueError(f"`y` must not be empty.")
123
-
124
- if x is None:
125
- x = np.arange(y.shape[0])
126
- elif x.ndim != 1:
127
- raise excp.InputValueError(f"`x` must be of dimension 1 not {x.ndim}.")
128
- elif x.shape[0] < 1:
129
- raise excp.InputValueError(f"`x` must not be empty.")
130
-
131
- if x.shape[0] != y.shape[0]:
132
- raise excp.InputValueError(f"`y` and `x` must be of same shape")
133
-
134
- # Scale the signal if needed
135
- if scale_y is not None:
136
- if len(scale_y) != 2:
137
- raise excp.InputValueError(f"`scale_y` must be tuple of two values (1, 2) => 1y+2")
138
- a, b = scale_y
139
- y = a * y + b
140
-
141
- # Create a figure
142
- if ax is None:
143
- fig, ax = plt.subplots(figsize=(15, 2))
144
- created_fig = True
145
- else:
146
- fig = ax.get_figure()
147
- created_fig = False
148
-
149
- # Plot the signal with right configurations
150
- plot_label = labels[0] if labels is not None and len(labels) > 0 else None
151
- if stem:
152
- ax.stem(x, y, linefmt=color, markerfmt='o', label=title)
153
- elif marker is not None:
154
- ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, marker=marker, label=title)
155
- else:
156
- ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, label=title)
157
-
158
- # Add legend
159
- if legend_loc is not None:
160
- ax.legend(loc=legend_loc)
161
-
162
- # Set the labels
163
- if title is not None:
164
- ax.set_title(title)
165
- if ylabel is not None:
166
- ax.set_ylabel(ylabel)
167
- if xlabel is not None:
168
- ax.set_xlabel(xlabel)
169
-
170
- # Applying axes limits into a region
171
- if ylim is not None:
172
- ax.set_ylim(ylim)
173
- if xlim is not None:
174
- ax.set_xlim(xlim)
175
-
176
- # Highlight a list of regions
177
- if highlight is not None:
178
- for highlight_region in highlight:
179
- if len(highlight_region) != 2:
180
- raise excp.InputValueError(f"`highlight should be a list of tuple of 2 values (left, right) => (1, 10.5)")
181
- l, r = highlight_region
182
- ax.add_patch(Rectangle((l, np.min(y)), r - l, np.max(y) - np.min(y), color='red', alpha=0.2, zorder=10))
183
-
184
- # Show/Return the figure as per needed
185
- if created_fig:
186
- fig.tight_layout()
187
- if Plotter._in_notebook():
188
- plt.close(fig)
189
- return fig
190
- else:
191
- plt.show()
192
- return fig
193
-
194
- @staticmethod
195
- @validate_args_type()
196
- def plot_matrix(
197
- M: np.ndarray,
198
- r: np.ndarray | None = None,
199
- c: np.ndarray | None = None,
200
- log_compression_factor: int | float | None = None,
201
- ax: plt.Axes | None = None,
202
- cmap: str = "gray_r",
203
- title: str | None = None,
204
- Mlabel: str | None = None,
205
- rlabel: str | None = None,
206
- clabel: str | None = None,
207
- rlim: tuple[float, float] | None = None,
208
- clim: tuple[float, float] | None = None,
209
- highlight: list[tuple[float, float, float, float]] | None = None,
210
- origin: str = "lower", # or "lower"
211
- show_colorbar: bool = True,
212
- cax: plt.Axes | None = None,
213
- show_grid: bool = True,
214
- tick_mode: str = "center", # "center" or "edge"
215
- n_ticks: tuple[int, int] | None = None,
216
- ) -> plt.Figure:
217
- """
218
- Plot a 2D matrix with optional zooming, highlighting, and grid.
219
-
220
- .. code-block:: python
221
-
222
- from modusa.io import Plotter
223
- import numpy as np
224
- import matplotlib.pyplot as plt
225
-
226
- # Create a 50x50 random matrix
227
- M = np.random.rand(50, 50)
228
-
229
- # Coordinate axes
230
- r = np.linspace(0, 1, M.shape[0])
231
- c = np.linspace(0, 1, M.shape[1])
232
-
233
- # Plot the matrix
234
- fig = Plotter.plot_matrix(
235
- M=M,
236
- r=r,
237
- c=c,
238
- log_compression_factor=None,
239
- ax=None,
240
- labels=None,
241
- zoom=None,
242
- highlight=None,
243
- cmap="viridis",
244
- origin="lower",
245
- show_colorbar=True,
246
- cax=None,
247
- show_grid=False,
248
- tick_mode="center",
249
- n_ticks=(5, 5),
250
- )
251
-
252
-
253
- Parameters
254
- ----------
255
- M: np.ndarray
256
- 2D matrix to plot.
257
- r: np.ndarray
258
- Row coordinate axes.
259
- c: np.ndarray
260
- Column coordinate axes.
261
- log_compression_factor: int | float | None
262
- Apply log compression to enhance contrast (if provided).
263
- ax: plt.Axes | None
264
- Matplotlib axis to draw on (creates new if None).
265
- labels: tuple[str, str, str, str] | None
266
- Labels for the plot (title, Mlabel, xlabel, ylabel).
267
- zoom: tuple[float, float, float, float] | None
268
- Zoom to (r1, r2, c1, c2) in matrix coordinates.
269
- highlight: list[tuple[float, float, float, float]] | None
270
- List of rectangles (r1, r2, c1, c2) to highlight.
271
- cmap: str
272
- Colormap to use.
273
- origin: str
274
- Image origin, e.g., "upper" or "lower".
275
- show_colorbar: bool
276
- Whether to display colorbar.
277
- cax: plt.Axes | None
278
- Axis to draw colorbar on (ignored if show_colorbar is False).
279
- show_grid: bool
280
- Whether to show grid lines.
281
- tick_mode: str
282
- Tick alignment mode: "center" or "edge".
283
- n_ticks: tuple[int, int]
284
- Number of ticks on row and column axes.
285
-
286
- Returns
287
- -------
288
- plt.Figure
289
- Matplotlib figure containing the plot.
290
-
291
- """
292
-
293
- # Validate the important args and get the signal that needs to be plotted
294
- if M.ndim != 2:
295
- raise excp.InputValueError(f"`M` must have 2 dimension not {M.ndim}")
296
- if r is None:
297
- r = M.shape[0]
298
- if c is None:
299
- c = M.shape[1]
300
-
301
- if r.ndim != 1 and c.ndim != 1:
302
- raise excp.InputValueError(f"`r` and `c` must have 2 dimension not r:{r.ndim}, c:{c.ndim}")
303
-
304
- if r.shape[0] != M.shape[0]:
305
- raise excp.InputValueError(f"`r` must have shape as `M row` not {r.shape}")
306
- if c.shape[0] != M.shape[1]:
307
- raise excp.InputValueError(f"`c` must have shape as `M column` not {c.shape}")
308
-
309
- # Scale the signal if needed
310
- if log_compression_factor is not None:
311
- M = np.log1p(float(log_compression_factor) * M)
312
-
313
- # Create a figure
314
- if ax is None:
315
- fig, ax = plt.subplots(figsize=(15, 4))
316
- created_fig = True
317
- else:
318
- fig = ax.get_figure()
319
- created_fig = False
320
-
321
- # Plot the signal with right configurations
322
- # Compute extent
323
- extent = Plotter._compute_centered_extent(r, c, origin)
324
-
325
- # Plot image
326
- im = ax.imshow(
327
- M,
328
- aspect="auto",
329
- cmap=cmap,
330
- origin=origin,
331
- extent=extent
332
- )
333
-
334
- # Set the ticks and labels
335
- if n_ticks is None:
336
- n_ticks = (10, 10)
337
-
338
- if tick_mode == "center":
339
- ax.yaxis.set_major_locator(MaxNLocator(nbins=n_ticks[0]))
340
- ax.xaxis.set_major_locator(MaxNLocator(nbins=n_ticks[1])) # limits ticks
341
-
342
- elif tick_mode == "edge":
343
- dr = np.diff(r).mean() if len(r) > 1 else 1
344
- dc = np.diff(c).mean() if len(c) > 1 else 1
345
-
346
- # Edge tick positions (centered)
347
- xticks_all = np.append(c, c[-1] + dc) - dc / 2
348
- yticks_all = np.append(r, r[-1] + dr) - dr / 2
349
-
350
- # Determine number of ticks
351
- nr, nc = n_ticks
352
-
353
- # Choose evenly spaced tick indices
354
- xtick_idx = np.linspace(0, len(xticks_all) - 1, nc, dtype=int)
355
- ytick_idx = np.linspace(0, len(yticks_all) - 1, nr, dtype=int)
356
-
357
- ax.set_xticks(xticks_all[xtick_idx])
358
- ax.set_yticks(yticks_all[ytick_idx])
359
-
360
- # Set the labels
361
- if title is not None:
362
- ax.set_title(title)
363
- if rlabel is not None:
364
- ax.set_ylabel(rlabel)
365
- if clabel is not None:
366
- ax.set_xlabel(clabel)
367
-
368
- # Applying axes limits into a region
369
- if rlim is not None:
370
- ax.set_ylim(rlim)
371
- if clim is not None:
372
- ax.set_xlim(clim)
373
-
374
- # Applying axes limits into a region
375
- if rlim is not None:
376
- ax.set_ylim(rlim)
377
- if clim is not None:
378
- ax.set_xlim(clim)
379
-
380
- # Highlight a list of regions
381
- if highlight is not None:
382
- for r1, r2, c1, c2 in highlight:
383
- row_min, row_max = min(r1, r2), max(r1, r2)
384
- col_min, col_max = min(c1, c2), max(c1, c2)
385
- width = col_max - col_min
386
- height = row_max - row_min
387
- ax.add_patch(Rectangle((col_min, row_min), width, height, color='red', alpha=0.2, zorder=10))
388
-
389
- # Show colorbar
390
- if show_colorbar is not None:
391
- cbar = fig.colorbar(im, ax=ax, cax=cax)
392
- if Mlabel is not None:
393
- cbar.set_label(Mlabel)
394
-
395
- # Show grid
396
- if show_grid:
397
- ax.grid(True, color="gray", linestyle="--", linewidth=0.5) # TODO
398
-
399
- # Show/Return the figure as per needed
400
- if created_fig:
401
- fig.tight_layout()
402
- if Plotter._in_notebook():
403
- plt.close(fig)
404
- return fig
405
- else:
406
- plt.show()
407
- return fig
408
-
409
- @staticmethod
410
- def _compute_centered_extent(r: np.ndarray, c: np.ndarray, origin: str) -> list[float]:
411
- """
412
-
413
- """
414
- dc = np.diff(c).mean() if len(c) > 1 else 1
415
- dr = np.diff(r).mean() if len(r) > 1 else 1
416
- left = c[0] - dc / 2
417
- right = c[-1] + dc / 2
418
- bottom = r[0] - dr / 2
419
- top = r[-1] + dr / 2
420
- return [left, right, top, bottom] if origin == "upper" else [left, right, bottom, top]
421
-
422
- @staticmethod
423
- def _in_notebook() -> bool:
424
- try:
425
- from IPython import get_ipython
426
- shell = get_ipython()
427
- return shell and shell.__class__.__name__ == "ZMQInteractiveShell"
428
- except ImportError:
429
- return False
430
-
@@ -1,139 +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.io.base import ModusaIO
7
- from typing import Any
8
- from pathlib import Path
9
- import yt_dlp
10
-
11
- class YoutubeDownloader(ModusaIO):
12
- """
13
- Download highest quality audio/video from YouTube.
14
-
15
- Note
16
- ----
17
- - The engine uses `yt_dlp` python package to download content from YouTube.
18
-
19
- """
20
-
21
- #--------Meta Information----------
22
- _name = "Youtube Downloader"
23
- _description = "Download highest quality audio/video from YouTube."
24
- _author_name = "Ankit Anand"
25
- _author_email = "ankit0.anand0@gmail.com"
26
- _created_at = "2025-07-05"
27
- #----------------------------------
28
-
29
- def __init__(self):
30
- super().__init__()
31
-
32
- @staticmethod
33
- @validate_args_type()
34
- def _download_audio(url: str, output_dir: str | Path):
35
- """
36
- Download the highest quality audio from a given YouTube URL.
37
-
38
- Parameters
39
- ----------
40
- url: str
41
- URL for the YouTube video.
42
- output_dir:
43
- Directory where the audio will be saved.
44
-
45
- Returns
46
- -------
47
- Path
48
- Path to the downloaded audio file.
49
- """
50
- output_dir = Path(output_dir)
51
- output_dir.mkdir(parents=True, exist_ok=True)
52
-
53
- ydl_opts = {
54
- 'format': 'bestaudio/best',
55
- 'outtmpl': f'{output_dir}/%(title)s.%(ext)s',
56
- 'quiet': True,
57
- }
58
-
59
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
60
- info = ydl.extract_info(url, download=True)
61
- return Path(info['requested_downloads'][0]['filepath'])
62
-
63
- @staticmethod
64
- @validate_args_type()
65
- def _download_video(url: str, output_dir: str | Path):
66
- """
67
- Download the highest quality video from a YouTube URL using yt-dlp.
68
-
69
- Parameters
70
- ----------
71
- url: str
72
- URL for the YouTube video.
73
- output_dir:
74
- Directory where the video will be saved.
75
-
76
- Returns
77
- -------
78
- Path
79
- Path to the downloaded audio file
80
- """
81
- output_dir = Path(output_dir)
82
- output_dir.mkdir(parents=True, exist_ok=True)
83
-
84
- ydl_opts = {
85
- 'format': 'bestvideo+bestaudio/best', # High quality
86
- 'outtmpl': str(output_dir / '%(title)s.%(ext)s'),
87
- 'merge_output_format': 'mp4',
88
- 'quiet': True, # Hide verbose output
89
- }
90
-
91
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
92
- info = ydl.extract_info(url, download=True)
93
- return Path(info['requested_downloads'][0]['filepath'])
94
-
95
- @staticmethod
96
- @validate_args_type()
97
- def download(url: str, content_type: str, output_dir: str | Path) -> Path:
98
- """
99
- Downloads audio/video from YouTube.
100
-
101
- .. code-block:: python
102
-
103
- # To download audio
104
- from modusa.io import YoutubeDownloader
105
- audio_fp = YoutubeDownloader.download(
106
- url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
107
- content_type="audio",
108
- output_dir="."
109
- )
110
-
111
- # To download video
112
- from modusa.engines import YoutubeDownloaderEngine
113
- video_fp = YoutubeDownloader.download(
114
- url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
115
- content_type="audio",
116
- output_dir="."
117
- )
118
-
119
- Parameters
120
- ----------
121
- url: str
122
- Link to the YouTube video.
123
- content_type: str
124
- "audio" or "video"
125
- output_dir: str | Path
126
- Directory to save the YouTube content.
127
-
128
- Returns
129
- -------
130
- Path
131
- File path of the downloaded content.
132
-
133
- """
134
- if content_type == "audio":
135
- return YoutubeDownloader._download_audio(url=url, output_dir=output_dir)
136
- elif content_type == "video":
137
- return YoutubeDownloader._download_video(url=url, output_dir=output_dir)
138
- else:
139
- raise excp.InputValueError(f"`content_type` can either take 'audio' or 'video' not {content_type}")
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from .base import ModusaSignal
4
- from .audio_signal import AudioSignal
5
- from .time_domain_signal import TimeDomainSignal
6
- from .frequency_domain_signal import FrequencyDomainSignal
7
- from .spectrogram import Spectrogram