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
@@ -1,629 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- import numpy as np
5
- import matplotlib.pyplot as plt
6
- import matplotlib.gridspec as gridspec
7
- from matplotlib.patches import Rectangle
8
- from mpl_toolkits.axes_grid1.inset_locator import inset_axes
9
-
10
- # Helper for 2D plot
11
- def _calculate_extent(x, y):
12
- # Handle spacing safely
13
- if len(x) > 1:
14
- dx = x[1] - x[0]
15
- else:
16
- dx = 1 # Default spacing for single value
17
- if len(y) > 1:
18
- dy = y[1] - y[0]
19
- else:
20
- dy = 1 # Default spacing for single value
21
-
22
- return [
23
- x[0] - dx / 2,
24
- x[-1] + dx / 2,
25
- y[0] - dy / 2,
26
- y[-1] + dy / 2
27
- ]
28
-
29
- # Helper to load fonts (devnagri)
30
- def set_default_hindi_font():
31
- """
32
- Hindi fonts works for both english and hindi.
33
- """
34
- from pathlib import Path
35
- import matplotlib as mpl
36
- import matplotlib.font_manager as fm
37
- # Path to your bundled font
38
- font_path = Path(__file__).resolve().parents[1] / "fonts" / "NotoSansDevanagari-Regular.ttf"
39
-
40
- # Register the font with matplotlib
41
- fm.fontManager.addfont(str(font_path))
42
-
43
- # Get the font family name from the file
44
- hindi_font = fm.FontProperties(fname=str(font_path))
45
-
46
- # Set as default rcParam
47
- mpl.rcParams['font.family'] = hindi_font.get_name()
48
-
49
- #set_default_hindi_font()
50
-
51
- #======== 1D ===========
52
- def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylabel=None, title=None, legend=None, fmt=None, show_grid=False, show_stem=False):
53
- """
54
- Plots a 1D signal using matplotlib.
55
-
56
- .. code-block:: python
57
-
58
- import modusa as ms
59
- import numpy as np
60
-
61
- x = np.arange(100) / 100
62
- y = np.sin(x)
63
-
64
- display(ms.plot1d(y, x))
65
-
66
-
67
- Parameters
68
- ----------
69
- *args : tuple[array-like, array-like] | tuple[array-like]
70
- - The signal y and axis x to be plotted.
71
- - If only values are provided, we generate the axis using arange.
72
- - E.g. (y1, x1), (y2, x2), ...
73
- ann : list[tuple[Number, Number, str] | None
74
- - A list of annotations to mark specific points. Each tuple should be of the form (start, end, label).
75
- - Default: None => No annotation.
76
- events : list[Number] | None
77
- - A list of x-values where vertical lines (event markers) will be drawn.
78
- - Default: None
79
- xlim : tuple[Number, Number] | None
80
- - Limits for the x-axis as (xmin, xmax).
81
- - Default: None
82
- ylim : tuple[Number, Number] | None
83
- - Limits for the y-axis as (ymin, ymax).
84
- - Default: None
85
- xlabel : str | None
86
- - Label for the x-axis.
87
- - - Default: None
88
- ylabel : str | None
89
- - Label for the y-axis.
90
- - Default: None
91
- title : str | None
92
- - Title of the plot.
93
- - Default: None
94
- legend : list[str] | None
95
- - List of legend labels corresponding to each signal if plotting multiple lines.
96
- - Default: None
97
- fmt: list[str] | None
98
- - linefmt for different line plots.
99
- - Default: None
100
- show_grid: bool
101
- - If you want to show the grid.
102
- - Default: False
103
- show_stem: bool:
104
- - If you want stem plot.
105
- - Default: False
106
-
107
- Returns
108
- -------
109
- plt.Figure
110
- Matplolib figure.
111
- """
112
- for arg in args:
113
- if len(arg) not in [1, 2]: # 1 if it just provides values, 2 if it provided axis as well
114
- raise ValueError(f"1D signal needs to have max 2 arrays (y, x) or simply (y, )")
115
-
116
- if isinstance(legend, str): legend = (legend, )
117
- if legend is not None:
118
- if len(legend) < len(args):
119
- raise ValueError(f"`legend` should be provided for each signal.")
120
-
121
- if isinstance(fmt, str): fmt = [fmt]
122
- if fmt is not None:
123
- if len(fmt) < len(args):
124
- raise ValueError(f"`fmt` should be provided for each signal.")
125
-
126
- colors = plt.get_cmap('tab10').colors
127
-
128
- fig = plt.figure(figsize=(16, 2))
129
- gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.2, 1])
130
-
131
- signal_ax = fig.add_subplot(gs[2, 0])
132
- annotation_ax = fig.add_subplot(gs[0, 0], sharex=signal_ax)
133
- events_ax = fig.add_subplot(gs[1, 0])
134
-
135
- # Set lim
136
- if xlim is not None:
137
- signal_ax.set_xlim(xlim)
138
-
139
- if ylim is not None:
140
- signal_ax.set_ylim(ylim)
141
-
142
- # Add signal plot
143
- for i, signal in enumerate(args):
144
- if len(signal) == 1:
145
- y = signal[0]
146
- x = np.arange(y.size)
147
- if legend is not None:
148
- if show_stem is True:
149
- markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
150
- markerline.set_color(colors[i])
151
- stemlines.set_color(colors[i])
152
- baseline.set_color("k")
153
- else:
154
- if fmt is not None:
155
- signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
156
- else:
157
- signal_ax.plot(x, y, color=colors[i], label=legend[i])
158
- else:
159
- if show_stem is True:
160
- markerline, stemlines, baseline = signal_ax.stem(x, y)
161
- markerline.set_color(colors[i])
162
- stemlines.set_color(colors[i])
163
- baseline.set_color("k")
164
- else:
165
- if fmt is not None:
166
- signal_ax.plot(x, y, fmt[i], markersize=4)
167
- else:
168
- signal_ax.plot(x, y, color=colors[i])
169
-
170
- elif len(signal) == 2:
171
- y, x = signal[0], signal[1]
172
- if legend is not None:
173
- if show_stem is True:
174
- markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
175
- markerline.set_color(colors[i])
176
- stemlines.set_color(colors[i])
177
- baseline.set_color("k")
178
- else:
179
- if fmt is not None:
180
- signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
181
- else:
182
- signal_ax.plot(x, y, color=colors[i], label=legend[i])
183
- else:
184
- if show_stem is True:
185
- markerline, stemlines, baseline = signal_ax.stem(x, y)
186
- markerline.set_color(colors[i])
187
- stemlines.set_color(colors[i])
188
- baseline.set_color("k")
189
- else:
190
- if fmt is not None:
191
- signal_ax.plot(x, y, fmt[i], markersize=4)
192
- else:
193
- signal_ax.plot(x, y, color=colors[i])
194
-
195
-
196
- # Add annotations
197
- if ann is not None:
198
- annotation_ax.set_ylim(0, 1) # For consistent layout
199
- # Determine visible x-range
200
- x_view_min = xlim[0] if xlim is not None else np.min(x)
201
- x_view_max = xlim[1] if xlim is not None else np.max(x)
202
-
203
- for i, (start, end, tag) in enumerate(ann):
204
- # We make sure that we only plot annotation that are within the x range of the current view
205
- if start >= x_view_max or end <= x_view_min:
206
- continue
207
-
208
- # Clip boundaries to xlim
209
- start = max(start, x_view_min)
210
- end = min(end, x_view_max)
211
-
212
- box_colors = ["gray", "lightgray"] # Alternates color between two
213
- box_color = box_colors[i % 2]
214
-
215
- width = end - start
216
- rect = Rectangle((start, 0), width, 1, color=box_color, alpha=0.7)
217
- annotation_ax.add_patch(rect)
218
-
219
- text_obj = annotation_ax.text(
220
- (start + end) / 2, 0.5, tag,
221
- ha='center', va='center',
222
- fontsize=10, color="black", fontweight='bold', zorder=10, clip_on=True
223
- )
224
-
225
- text_obj.set_clip_path(rect)
226
-
227
-
228
- # Add vlines
229
- if events is not None:
230
- # if not isinstance(events, tuple):
231
- # raise TypeError(f"`events` should be tuple, got {type(events)}")
232
-
233
- for xpos in events:
234
- if xlim is not None:
235
- if xlim[0] <= xpos <= xlim[1]:
236
- annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
237
- else:
238
- annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
239
-
240
- # Add legend
241
- if legend is not None:
242
- handles, labels = signal_ax.get_legend_handles_labels()
243
- fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.2), ncol=len(legend), frameon=True)
244
-
245
- # Set title, labels
246
- if title is not None:
247
- annotation_ax.set_title(title, pad=10, size=11)
248
- if xlabel is not None:
249
- signal_ax.set_xlabel(xlabel)
250
- if ylabel is not None:
251
- signal_ax.set_ylabel(ylabel)
252
-
253
- # Add grid to the plot
254
- if show_grid is True:
255
- signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
256
-
257
- # Remove the boundaries and ticks from an axis
258
- if ann is not None:
259
- annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
260
- else:
261
- annotation_ax.axis("off")
262
-
263
- if events is not None:
264
- events_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
265
- else:
266
- events_ax.axis("off")
267
-
268
-
269
- fig.subplots_adjust(hspace=0.01, wspace=0.05)
270
- plt.close()
271
- return fig
272
-
273
- #======== 2D ===========
274
- def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", Mlabel=None, xlabel=None, ylabel=None, title=None, legend=None, lm=False, show_grid=False):
275
- """
276
- Plots a 2D matrix (e.g., spectrogram or heatmap) with optional annotations and events.
277
-
278
- .. code-block:: python
279
-
280
- import modusa as ms
281
- import numpy as np
282
-
283
- M = np.random.random((10, 30))
284
- y = np.arange(M.shape[0])
285
- x = np.arange(M.shape[1])
286
-
287
- display(ms.plot2d(M, y, x))
288
-
289
- Parameters
290
- ----------
291
- *args : tuple[array-like, array-like]
292
- - The signal values to be plotted.
293
- - E.g. (M1, y1, x1), (M2, y2, x2), ...
294
- ann : list[tuple[Number, Number, str]] | None
295
- - A list of annotation spans. Each tuple should be (start, end, label).
296
- - Default: None (no annotations).
297
- events : list[Number] | None
298
- - X-values where vertical event lines will be drawn.
299
- - Default: None.
300
- xlim : tuple[Number, Number] | None
301
- - Limits for the x-axis as (xmin, xmax).
302
- - Default: None (auto-scaled).
303
- ylim : tuple[Number, Number] | None
304
- - Limits for the y-axis as (ymin, ymax).
305
- - Default: None (auto-scaled).
306
- origin : {'upper', 'lower'}
307
- - Origin position for the image display. Used in `imshow`.
308
- - Default: "lower".
309
- Mlabel : str | None
310
- - Label for the colorbar (e.g., "Magnitude", "Energy").
311
- - Default: None.
312
- xlabel : str | None
313
- - Label for the x-axis.
314
- - Default: None.
315
- ylabel : str | None
316
- - Label for the y-axis.
317
- - Default: None.
318
- title : str | None
319
- - Title of the plot.
320
- - Default: None.
321
- legend : list[str] | None
322
- - Legend labels for any overlaid lines or annotations.
323
- - Default: None.
324
- lm: bool
325
- - Adds a circular marker for the line.
326
- - Default: False
327
- - Useful to show the data points.
328
- show_grid: bool
329
- - If you want to show the grid.
330
- - Default: False
331
-
332
- Returns
333
- -------
334
- matplotlib.figure.Figure
335
- The matplotlib Figure object.
336
- """
337
-
338
- for arg in args:
339
- if len(arg) not in [1, 2, 3]: # Either provide just the matrix or with both axes info
340
- raise ValueError(f"Data to plot needs to have 3 arrays (M, y, x)")
341
- if isinstance(legend, str): legend = (legend, )
342
-
343
- fig = plt.figure(figsize=(16, 4))
344
- gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.1, 1]) # colorbar, annotation, signal
345
-
346
- colors = plt.get_cmap('tab10').colors
347
-
348
- signal_ax = fig.add_subplot(gs[2, 0])
349
- annotation_ax = fig.add_subplot(gs[1, 0], sharex=signal_ax)
350
-
351
- colorbar_ax = fig.add_subplot(gs[0, 0])
352
- colorbar_ax.axis("off")
353
-
354
-
355
- # Add lim
356
- if xlim is not None:
357
- signal_ax.set_xlim(xlim)
358
-
359
- if ylim is not None:
360
- signal_ax.set_ylim(ylim)
361
-
362
- # Add signal plot
363
- i = 0 # This is to track the legend for 1D plots
364
- for signal in args:
365
-
366
- data = signal[0] # This can be 1D or 2D (1D meaning we have to overlay on the matrix)
367
-
368
- if data.ndim == 1: # 1D
369
- if len(signal) == 1: # It means that the axis was not passed
370
- x = np.arange(data.shape[0])
371
- else:
372
- x = signal[1]
373
-
374
- if lm is False:
375
- if legend is not None:
376
- signal_ax.plot(x, data, label=legend[i])
377
- signal_ax.legend(loc="upper right")
378
- else:
379
- signal_ax.plot(x, data)
380
- else:
381
- if legend is not None:
382
- signal_ax.plot(x, data, marker="o", markersize=7, markerfacecolor='red', linestyle="--", linewidth=2, label=legend[i])
383
- signal_ax.legend(loc="upper right")
384
- else:
385
- signal_ax.plot(x, data, marker="o", markersize=7, markerfacecolor='red', linestyle="--", linewidth=2)
386
-
387
- i += 1
388
-
389
- elif data.ndim == 2: # 2D
390
- M = data
391
- if len(signal) == 1: # It means that the axes were not passed
392
- y = np.arange(M.shape[0])
393
- x = np.arange(M.shape[1])
394
- extent = _calculate_extent(x, y)
395
- im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
396
-
397
- elif len(signal) == 3: # It means that the axes were passed
398
- M, y, x = signal[0], signal[1], signal[2]
399
- extent = _calculate_extent(x, y)
400
- im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
401
-
402
- # Add annotations
403
- if ann is not None:
404
- annotation_ax.set_ylim(0, 1) # For consistent layout
405
- # Determine visible x-range
406
- x_view_min = xlim[0] if xlim is not None else np.min(x)
407
- x_view_max = xlim[1] if xlim is not None else np.max(x)
408
-
409
- for i, (start, end, tag) in enumerate(ann):
410
- # We make sure that we only plot annotation that are within the x range of the current view
411
- if start >= x_view_max or end <= x_view_min:
412
- continue
413
-
414
- # Clip boundaries to xlim
415
- start = max(start, x_view_min)
416
- end = min(end, x_view_max)
417
-
418
- color = colors[i % len(colors)]
419
- width = end - start
420
- rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
421
- annotation_ax.add_patch(rect)
422
- text_obj = annotation_ax.text(
423
- (start + end) / 2, 0.5, tag,
424
- ha='center', va='center',
425
- fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True
426
- )
427
-
428
- text_obj.set_clip_path(rect)
429
-
430
- # Add vlines
431
- if events is not None:
432
- for xpos in events:
433
- if xlim is not None:
434
- if xlim[0] <= xpos <= xlim[1]:
435
- annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
436
- else:
437
- annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
438
-
439
- # Add legend incase there are 1D overlays
440
- if legend is not None:
441
- handles, labels = signal_ax.get_legend_handles_labels()
442
- if handles: # Only add legend if there's something to show
443
- signal_ax.legend(handles, labels, loc="upper right")
444
-
445
- # Add colorbar
446
- # Create an inset axis on top-right of signal_ax
447
- cax = inset_axes(
448
- colorbar_ax,
449
- width="20%", # percentage of parent width
450
- height="20%", # height in percentage of parent height
451
- loc='upper right',
452
- bbox_to_anchor=(0, 0, 1, 1),
453
- bbox_transform=colorbar_ax.transAxes,
454
- borderpad=1
455
- )
456
-
457
- cbar = plt.colorbar(im, cax=cax, orientation='horizontal')
458
- cbar.ax.xaxis.set_ticks_position('top')
459
-
460
- if Mlabel is not None:
461
- cbar.set_label(Mlabel, labelpad=5)
462
-
463
-
464
- # Set title, labels
465
- if title is not None:
466
- annotation_ax.set_title(title, pad=10, size=11)
467
- if xlabel is not None:
468
- signal_ax.set_xlabel(xlabel)
469
- if ylabel is not None:
470
- signal_ax.set_ylabel(ylabel)
471
-
472
- # Add grid to the plot
473
- if show_grid is True:
474
- signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
475
-
476
- # Making annotation axis spines thicker
477
- if ann is not None:
478
- annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
479
- else:
480
- annotation_ax.axis("off")
481
-
482
- fig.subplots_adjust(hspace=0.01, wspace=0.05)
483
- plt.close()
484
- return fig
485
-
486
- #======== Plot distribution ===========
487
- def plot_dist(*args, ann=None, xlim=None, ylim=None, ylabel=None, xlabel=None, title=None, legend=None, show_hist=True, npoints=200, bins=30):
488
- """
489
- Plot distribution.
490
-
491
- .. code-block:: python
492
-
493
- import modusa as ms
494
- import numpy as np
495
- np.random.seed(42)
496
- data = np.random.normal(loc=1, scale=1, size=1000)
497
- ms.plot_dist(data, data+5, data-10, ann=[(0, 1, "A")], legend=("D1", "D2", "D3"), ylim=(0, 1), xlabel="X", ylabel="Counts", title="Distribution")
498
-
499
- Parameters
500
- ----------
501
- *args: ndarray
502
- - Data arrays for which distribution needs to be plotted.
503
- - Arrays will be flattened.
504
- ann : list[tuple[Number, Number, str] | None
505
- - A list of annotations to mark specific points. Each tuple should be of the form (start, end, label).
506
- - Default: None => No annotation.
507
- events : list[Number] | None
508
- - A list of x-values where vertical lines (event markers) will be drawn.
509
- - Default: None
510
- xlim : tuple[Number, Number] | None
511
- - Limits for the x-axis as (xmin, xmax).
512
- - Default: None
513
- ylim : tuple[Number, Number] | None
514
- - Limits for the y-axis as (ymin, ymax).
515
- - Default: None
516
- xlabel : str | None
517
- - Label for the x-axis.
518
- - - Default: None
519
- ylabel : str | None
520
- - Label for the y-axis.
521
- - Default: None
522
- title : str | None
523
- - Title of the plot.
524
- - Default: None
525
- legend : list[str] | None
526
- - List of legend labels corresponding to each signal if plotting multiple distributions.
527
- - Default: None
528
- show_hist: bool
529
- - Want to show histogram as well.
530
- npoints: int
531
- - Number of points for which gaussian needs to be computed between min and max.
532
- - Higher value means more points are evaluated with the fitted gaussian, thereby higher resolution.
533
- bins: int
534
- - The number of bins for histogram.
535
- - This is used only to plot the histogram.
536
-
537
- Returns
538
- -------
539
- plt.Figure
540
- - Matplotlib figure.
541
- """
542
- from scipy.stats import gaussian_kde
543
-
544
- if isinstance(legend, str):
545
- legend = (legend, )
546
-
547
- if legend is not None:
548
- if len(legend) < len(args):
549
- raise ValueError(f"Legend should be provided for each signal.")
550
-
551
- # Create figure
552
- fig = plt.figure(figsize=(16, 4))
553
- gs = gridspec.GridSpec(2, 1, height_ratios=[0.1, 1])
554
-
555
- colors = plt.get_cmap('tab10').colors
556
-
557
- dist_ax = fig.add_subplot(gs[1, 0])
558
- annotation_ax = fig.add_subplot(gs[0, 0], sharex=dist_ax)
559
-
560
- # Set limits
561
- if xlim is not None:
562
- dist_ax.set_xlim(xlim)
563
-
564
- if ylim is not None:
565
- dist_ax.set_ylim(ylim)
566
-
567
- # Add plot
568
- for i, data in enumerate(args):
569
- # Fit gaussian to the data
570
- kde = gaussian_kde(data)
571
-
572
- # Create points to evaluate KDE
573
- x = np.linspace(np.min(data), np.max(data), npoints)
574
- y = kde(x)
575
-
576
- if legend is not None:
577
- dist_ax.plot(x, y, color=colors[i], label=legend[i])
578
- if show_hist is True:
579
- dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black', label=legend[i])
580
- else:
581
- dist_ax.plot(x, y, color=colors[i])
582
- if show_hist is True:
583
- dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black')
584
-
585
- # Add annotations
586
- if ann is not None:
587
- annotation_ax.set_ylim(0, 1) # For consistent layout
588
- # Determine visible x-range
589
- x_view_min = xlim[0] if xlim is not None else np.min(x)
590
- x_view_max = xlim[1] if xlim is not None else np.max(x)
591
- for i, (start, end, tag) in enumerate(ann):
592
- # We make sure that we only plot annotation that are within the x range of the current view
593
- if start >= x_view_max or end <= x_view_min:
594
- continue
595
-
596
- # Clip boundaries to xlim
597
- start = max(start, x_view_min)
598
- end = min(end, x_view_max)
599
-
600
- color = colors[i % len(colors)]
601
- width = end - start
602
- rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
603
- annotation_ax.add_patch(rect)
604
-
605
- text_obj = annotation_ax.text((start + end) / 2, 0.5, tag, ha='center', va='center', fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True)
606
- text_obj.set_clip_path(rect)
607
-
608
- # Add legend
609
- if legend is not None:
610
- handles, labels = dist_ax.get_legend_handles_labels()
611
- fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.1), ncol=len(legend), frameon=True)
612
-
613
- # Set title, labels
614
- if title is not None:
615
- annotation_ax.set_title(title, pad=10, size=11)
616
- if xlabel is not None:
617
- dist_ax.set_xlabel(xlabel)
618
- if ylabel is not None:
619
- dist_ax.set_ylabel(ylabel)
620
-
621
- # Remove the boundaries and ticks from annotation axis
622
- if ann is not None:
623
- annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
624
- else:
625
- annotation_ax.axis("off")
626
-
627
- fig.subplots_adjust(hspace=0.01, wspace=0.05)
628
- plt.close()
629
- return fig
@@ -1,30 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- #---------------------------------
4
- # Author: Ankit Anand
5
- # Date: 13/09/25
6
- # Email: ankit0.anand0@gmail.com
7
- #---------------------------------
8
-
9
-
10
- def save(y, sr, out_path):
11
- """
12
- Saves an array as an audio file.
13
-
14
- Parameters
15
- ----------
16
- y: ndarray
17
- - Audio signal array.
18
- sr: number [> 0]
19
- - Sampling rate of the audio signal.
20
- out_path: str
21
- - Output path of the audio file.
22
-
23
- Returns
24
- -------
25
- None
26
- """
27
-
28
- import soundfile as sf
29
-
30
- sf.write(file=out_path, data=y, samplerate=sr)
modusa/tools/base.py DELETED
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from abc import ABC, abstractmethod
4
-
5
- class ModusaTool(ABC):
6
- """
7
- Base class for all tool: youtube downloader, audio converter, filter.
8
-
9
- >>> modusa-dev create io
10
-
11
- .. code-block:: python
12
-
13
- # General template of a subclass of ModusaTool
14
- from modusa.tools.base import ModusaTool
15
-
16
- class MyCustomIOClass(ModusaIO):
17
- #--------Meta Information----------
18
- _name = "My Custom Tool"
19
- _description = "My custom class for Tool."
20
- _author_name = "Ankit Anand"
21
- _author_email = "ankit0.anand0@gmail.com"
22
- _created_at = "2025-07-06"
23
- #----------------------------------
24
-
25
- @staticmethod
26
- def do_something():
27
- pass
28
-
29
-
30
- Note
31
- ----
32
- - This class is intended to be subclassed by any tool built for the modusa framework.
33
- - In order to create a tool, you can use modusa-dev CLI to generate a template.
34
- - It is recommended to treat subclasses of ModusaTool as namespaces and define @staticmethods with control parameters, rather than using instance-level __init__ methods.
35
- """
36
-
37
- #--------Meta Information----------
38
- _name: str = "Modusa Tool"
39
- _description: str = "Base class for any tool in the Modusa framework."
40
- _author_name = "Ankit Anand"
41
- _author_email = "ankit0.anand0@gmail.com"
42
- _created_at = "2025-07-11"
43
- #----------------------------------