modusa 0.4.29__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.
- modusa/__init__.py +9 -8
- modusa/tools/__init__.py +7 -2
- modusa/tools/ann_saver.py +30 -0
- modusa/tools/audio_recorder.py +0 -1
- modusa/tools/youtube_downloader.py +1 -4
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/METADATA +2 -2
- modusa-0.4.30.dist-info/RECORD +21 -0
- pyproject.toml +2 -2
- modusa/config.py +0 -18
- modusa/decorators.py +0 -176
- modusa/devtools/generate_docs_source.py +0 -92
- modusa/devtools/generate_template.py +0 -144
- modusa/devtools/list_authors.py +0 -2
- modusa/devtools/list_plugins.py +0 -60
- modusa/devtools/main.py +0 -45
- modusa/devtools/templates/generator.py +0 -24
- modusa/devtools/templates/io.py +0 -24
- modusa/devtools/templates/model.py +0 -47
- modusa/devtools/templates/plugin.py +0 -41
- modusa/devtools/templates/test.py +0 -10
- modusa/devtools/templates/tool.py +0 -24
- modusa/generators/__init__.py +0 -13
- modusa/generators/audio.py +0 -188
- modusa/generators/audio_waveforms.py +0 -236
- modusa/generators/base.py +0 -29
- modusa/generators/ftds.py +0 -298
- modusa/generators/s1d.py +0 -270
- modusa/generators/s2d.py +0 -300
- modusa/generators/s_ax.py +0 -102
- modusa/generators/t_ax.py +0 -64
- modusa/generators/tds.py +0 -267
- modusa/models/__init__.py +0 -14
- modusa/models/audio.py +0 -90
- modusa/models/base.py +0 -70
- modusa/models/data.py +0 -457
- modusa/models/ftds.py +0 -584
- modusa/models/s1d.py +0 -578
- modusa/models/s2d.py +0 -619
- modusa/models/s_ax.py +0 -448
- modusa/models/t_ax.py +0 -335
- modusa/models/tds.py +0 -465
- modusa/plugins/__init__.py +0 -3
- modusa/plugins/base.py +0 -100
- modusa/tools/_plotter_old.py +0 -629
- modusa/tools/audio_saver.py +0 -30
- modusa/tools/base.py +0 -43
- modusa/tools/math_ops.py +0 -335
- modusa/utils/__init__.py +0 -1
- modusa/utils/config.py +0 -25
- modusa/utils/excp.py +0 -49
- modusa/utils/logger.py +0 -18
- modusa/utils/np_func_cat.py +0 -44
- modusa/utils/plot.py +0 -142
- modusa-0.4.29.dist-info/RECORD +0 -65
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/WHEEL +0 -0
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/entry_points.txt +0 -0
- {modusa-0.4.29.dist-info → modusa-0.4.30.dist-info}/licenses/LICENSE.md +0 -0
modusa/tools/_plotter_old.py
DELETED
@@ -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
|
modusa/tools/audio_saver.py
DELETED
@@ -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
|
-
#----------------------------------
|