modusa 0.2.22__py3-none-any.whl → 0.2.23__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/decorators.py +4 -4
- modusa/devtools/docs/source/generators/audio_waveforms.rst +8 -0
- modusa/devtools/docs/source/generators/base.rst +8 -0
- modusa/devtools/docs/source/generators/index.rst +8 -0
- modusa/devtools/docs/source/io/audio_loader.rst +8 -0
- modusa/devtools/docs/source/io/base.rst +8 -0
- modusa/devtools/docs/source/io/index.rst +8 -0
- modusa/devtools/docs/source/plugins/base.rst +8 -0
- modusa/devtools/docs/source/plugins/index.rst +7 -0
- modusa/devtools/docs/source/signals/audio_signal.rst +8 -0
- modusa/devtools/docs/source/signals/base.rst +8 -0
- modusa/devtools/docs/source/signals/frequency_domain_signal.rst +8 -0
- modusa/devtools/docs/source/signals/index.rst +11 -0
- modusa/devtools/docs/source/signals/spectrogram.rst +8 -0
- modusa/devtools/docs/source/signals/time_domain_signal.rst +8 -0
- modusa/devtools/docs/source/tools/audio_converter.rst +8 -0
- modusa/devtools/docs/source/tools/audio_player.rst +8 -0
- modusa/devtools/docs/source/tools/base.rst +8 -0
- modusa/devtools/docs/source/tools/fourier_tranform.rst +8 -0
- modusa/devtools/docs/source/tools/index.rst +13 -0
- modusa/devtools/docs/source/tools/math_ops.rst +8 -0
- modusa/devtools/docs/source/tools/plotter.rst +8 -0
- modusa/devtools/docs/source/tools/youtube_downloader.rst +8 -0
- modusa/devtools/generate_doc_source.py +96 -0
- modusa/devtools/generate_template.py +8 -8
- modusa/devtools/main.py +3 -2
- modusa/devtools/templates/test.py +2 -3
- modusa/devtools/templates/{engine.py → tool.py} +3 -8
- modusa/generators/__init__.py +0 -2
- modusa/generators/audio_waveforms.py +22 -13
- modusa/generators/base.py +1 -1
- modusa/io/__init__.py +1 -5
- modusa/io/audio_loader.py +3 -33
- modusa/main.py +0 -30
- modusa/signals/__init__.py +1 -5
- modusa/signals/audio_signal.py +181 -124
- modusa/signals/base.py +1 -8
- modusa/signals/frequency_domain_signal.py +140 -93
- modusa/signals/spectrogram.py +197 -98
- modusa/signals/time_domain_signal.py +177 -74
- modusa/tools/__init__.py +2 -0
- modusa/{io → tools}/audio_converter.py +12 -4
- modusa/tools/audio_player.py +114 -0
- modusa/tools/base.py +43 -0
- modusa/tools/fourier_tranform.py +24 -0
- modusa/tools/math_ops.py +232 -0
- modusa/{io → tools}/plotter.py +155 -42
- modusa/{io → tools}/youtube_downloader.py +2 -2
- modusa/utils/excp.py +9 -42
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/METADATA +1 -1
- modusa-0.2.23.dist-info/RECORD +70 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +0 -3
- modusa/engines/base.py +0 -14
- modusa/io/audio_player.py +0 -72
- modusa/signals/signal_ops.py +0 -158
- modusa-0.2.22.dist-info/RECORD +0 -47
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/WHEEL +0 -0
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.22.dist-info → modusa-0.2.23.dist-info}/licenses/LICENSE.md +0 -0
modusa/tools/math_ops.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from modusa import excp
|
|
4
|
+
from modusa.tools.base import ModusaTool
|
|
5
|
+
from typing import Any
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
class MathOps(ModusaTool):
|
|
9
|
+
"""
|
|
10
|
+
Performs arithmetic and NumPy-style ops.
|
|
11
|
+
|
|
12
|
+
Note
|
|
13
|
+
----
|
|
14
|
+
- Shape-changing operations like reshape, transpose, etc. are not yet supported. Use only element-wise or aggregation ops for now.
|
|
15
|
+
- Index alignment must be handled carefully in future extensions.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def _axes_match(a1: tuple[np.ndarray, ...], a2: tuple[np.ndarray, ...]) -> bool:
|
|
19
|
+
"""
|
|
20
|
+
To check if two axes are same.
|
|
21
|
+
|
|
22
|
+
It checks the length of the axes and the corresponding values.
|
|
23
|
+
"""
|
|
24
|
+
if len(a1) != len(a2):
|
|
25
|
+
return False
|
|
26
|
+
return all(np.allclose(x, y, atol=1e-8) for x, y in zip(a1, a2))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#----------------------------------
|
|
30
|
+
# To handle basic element wise
|
|
31
|
+
# math operations like
|
|
32
|
+
# +, -, *, **, / ...
|
|
33
|
+
#----------------------------------
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def add(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
37
|
+
try:
|
|
38
|
+
result = np.add(a, b)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise excp.InputError(f"`a` and `b` can't be added") from e
|
|
41
|
+
|
|
42
|
+
if isinstance(a, str) and isinstance(b, str): # numpy actually concatenates, we do not want that
|
|
43
|
+
raise excp.InputError(f"`a` and `b` can't be added")
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def subtract(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
48
|
+
try:
|
|
49
|
+
result = np.subtract(a, b)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise excp.InputError(f"`a` and `b` can't be subtracted") from e
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def multiply(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
56
|
+
try:
|
|
57
|
+
result = np.multiply(a, b)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise excp.InputError(f"`a` and `b` can't be multiplied") from e
|
|
60
|
+
|
|
61
|
+
if isinstance(a, str) and isinstance(b, str): # numpy actually concatenates, we do not want that
|
|
62
|
+
raise excp.InputError(f"`a` and `b` can't be multiplied")
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def divide(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
67
|
+
try:
|
|
68
|
+
result = np.divide(a, b)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise excp.InputError(f"`a` and `b` can't be divided") from e
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def power(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
75
|
+
try:
|
|
76
|
+
result = np.power(a, b)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise excp.InputError(f"`a` can't be exponentiated with `b`") from e
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def floor_divide(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
83
|
+
try:
|
|
84
|
+
result = np.floor_divide(a, b)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise excp.InputError(f"`a` can't be floor divided by `b`") from e
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
#----------------------------------
|
|
90
|
+
# To handle numpy aggregator ops
|
|
91
|
+
#----------------------------------
|
|
92
|
+
@staticmethod
|
|
93
|
+
def mean(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
94
|
+
try:
|
|
95
|
+
result = np.mean(a, axis=axis)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise excp.InputError(f"can't find mean for `a`") from e
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def std(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
102
|
+
""""""
|
|
103
|
+
try:
|
|
104
|
+
result = np.std(a, axis=axis)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
raise excp.InputError(f"can't find std for `a`") from e
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def min(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
111
|
+
try:
|
|
112
|
+
result = np.min(a, axis=axis)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise excp.InputError(f"can't find min for `a`") from e
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def max(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
119
|
+
try:
|
|
120
|
+
result = np.max(a, axis=axis)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
raise excp.InputError(f"can't find max for `a`") from e
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def sum(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
127
|
+
try:
|
|
128
|
+
result = np.sum(a, axis=axis)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise excp.InputError(f"can't find sum for `a`") from e
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
#----------------------------------
|
|
134
|
+
# To handle numpy ops where the
|
|
135
|
+
# shapes are unaltered
|
|
136
|
+
# sin, cos, exp, log, ...
|
|
137
|
+
#----------------------------------
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def sin(a: Any) -> np.generic | np.ndarray:
|
|
141
|
+
try:
|
|
142
|
+
result = np.sin(a)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise excp.InputError(f"can't find sin for `a`") from e
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def cos(a: Any) -> np.generic | np.ndarray:
|
|
149
|
+
try:
|
|
150
|
+
result = np.cos(a)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
raise excp.InputError(f"can't find cos for `a`") from e
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def tanh(a: Any) -> np.generic | np.ndarray:
|
|
157
|
+
try:
|
|
158
|
+
result = np.tanh(a)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise excp.InputError(f"can't find tanh for `a`") from e
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def exp(a: Any) -> np.generic | np.ndarray:
|
|
165
|
+
try:
|
|
166
|
+
result = np.exp(a)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
raise excp.InputError(f"can't find exp for `a`") from e
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def log(a: Any) -> np.generic | np.ndarray:
|
|
173
|
+
try:
|
|
174
|
+
result = np.log(a)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise excp.InputError(f"can't find log for `a`") from e
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def log10(a: Any) -> np.generic | np.ndarray:
|
|
181
|
+
try:
|
|
182
|
+
result = np.log10(a)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise excp.InputError(f"can't find log10 for `a`") from e
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def log2(a: Any) -> np.generic | np.ndarray:
|
|
189
|
+
try:
|
|
190
|
+
result = np.log2(a)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise excp.InputError(f"can't find log2 for `a`") from e
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def log1p(a: Any) -> np.generic | np.ndarray:
|
|
197
|
+
try:
|
|
198
|
+
result = np.log1p(a)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
raise excp.InputError(f"can't find log1p for `a`") from e
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def sqrt(a: Any) -> np.generic | np.ndarray:
|
|
206
|
+
try:
|
|
207
|
+
result = np.sqrt(a)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise excp.InputError(f"can't find sqrt for `a`") from e
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def abs(a: Any) -> np.generic | np.ndarray:
|
|
214
|
+
try:
|
|
215
|
+
result = np.abs(a)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise excp.InputError(f"can't find abs for `a`") from e
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
#------------------------------------
|
|
221
|
+
# TODO: Add shape-changing ops like
|
|
222
|
+
# reshape, transpose, squeeze later
|
|
223
|
+
#------------------------------------
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def reshape(a: Any, shape: int | tuple[int, ...]) -> np.ndarray:
|
|
227
|
+
try:
|
|
228
|
+
result = np.reshape(a, shape=shape)
|
|
229
|
+
except Exception as e:
|
|
230
|
+
raise excp.InputError(f"can't reshape `a`") from e
|
|
231
|
+
return result
|
|
232
|
+
|
modusa/{io → tools}/plotter.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.
|
|
6
|
+
from modusa.tools.base import ModusaTool
|
|
7
7
|
import numpy as np
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
9
|
from matplotlib.patches import Rectangle
|
|
@@ -13,7 +13,7 @@ import warnings
|
|
|
13
13
|
warnings.filterwarnings("ignore", message="Glyph .* missing from font.*") # To supress any font related warnings, TODO: Add support to Devnagri font
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class Plotter(
|
|
16
|
+
class Plotter(ModusaTool):
|
|
17
17
|
"""
|
|
18
18
|
Plots different kind of signals using `matplotlib`.
|
|
19
19
|
|
|
@@ -35,20 +35,20 @@ class Plotter(ModusaIO):
|
|
|
35
35
|
def plot_signal(
|
|
36
36
|
y: np.ndarray,
|
|
37
37
|
x: np.ndarray | None,
|
|
38
|
-
scale_y: tuple[float, float] | None = None,
|
|
39
38
|
ax: plt.Axes | None = None,
|
|
40
|
-
|
|
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,
|
|
39
|
+
fmt: str = "k",
|
|
46
40
|
title: str | None = None,
|
|
41
|
+
label: str | None = None,
|
|
47
42
|
ylabel: str | None = None,
|
|
48
43
|
xlabel: str | None = None,
|
|
49
44
|
ylim: tuple[float, float] | None = None,
|
|
50
45
|
xlim: tuple[float, float] | None = None,
|
|
51
46
|
highlight: list[tuple[float, float], ...] | None = None,
|
|
47
|
+
vlines: list[float] | None = None,
|
|
48
|
+
hlines: list[float] | None = None,
|
|
49
|
+
show_grid: bool = False,
|
|
50
|
+
stem: bool = False,
|
|
51
|
+
legend_loc: str = None,
|
|
52
52
|
) -> plt.Figure | None:
|
|
53
53
|
"""
|
|
54
54
|
Plots 1D signal using `matplotlib` with various settings passed through the
|
|
@@ -67,7 +67,6 @@ class Plotter(ModusaIO):
|
|
|
67
67
|
fig = Plotter.plot_signal(
|
|
68
68
|
y=y,
|
|
69
69
|
x=x,
|
|
70
|
-
scale_y=None,
|
|
71
70
|
ax=None,
|
|
72
71
|
color="blue",
|
|
73
72
|
marker=None,
|
|
@@ -86,8 +85,6 @@ class Plotter(ModusaIO):
|
|
|
86
85
|
The signal values to plot on the y-axis.
|
|
87
86
|
x: np.ndarray | None
|
|
88
87
|
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
88
|
ax: plt.Axes | None
|
|
92
89
|
matplotlib Axes object to draw on. If None, a new figure and axis are created. Return type depends on parameter value.
|
|
93
90
|
color: str
|
|
@@ -131,33 +128,31 @@ class Plotter(ModusaIO):
|
|
|
131
128
|
if x.shape[0] != y.shape[0]:
|
|
132
129
|
raise excp.InputValueError(f"`y` and `x` must be of same shape")
|
|
133
130
|
|
|
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
131
|
# Create a figure
|
|
142
132
|
if ax is None:
|
|
143
133
|
fig, ax = plt.subplots(figsize=(15, 2))
|
|
144
134
|
created_fig = True
|
|
145
135
|
else:
|
|
146
136
|
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
|
-
|
|
137
|
+
created_fig = False
|
|
138
|
+
|
|
158
139
|
# Add legend
|
|
159
|
-
if
|
|
140
|
+
if label is not None:
|
|
141
|
+
legend_loc = legend_loc or "best"
|
|
142
|
+
# Plot the signal and attach the label
|
|
143
|
+
if stem:
|
|
144
|
+
ax.stem(x, y, linefmt="k", markerfmt='o', label=label)
|
|
145
|
+
else:
|
|
146
|
+
ax.plot(x, y, fmt, lw=1.5, ms=3, label=label)
|
|
160
147
|
ax.legend(loc=legend_loc)
|
|
148
|
+
else:
|
|
149
|
+
# Plot the signal without label
|
|
150
|
+
if stem:
|
|
151
|
+
ax.stem(x, y, linefmt="k", markerfmt='o')
|
|
152
|
+
else:
|
|
153
|
+
ax.plot(x, y, fmt, lw=1.5, ms=3)
|
|
154
|
+
|
|
155
|
+
|
|
161
156
|
|
|
162
157
|
# Set the labels
|
|
163
158
|
if title is not None:
|
|
@@ -173,21 +168,76 @@ class Plotter(ModusaIO):
|
|
|
173
168
|
if xlim is not None:
|
|
174
169
|
ax.set_xlim(xlim)
|
|
175
170
|
|
|
176
|
-
# Highlight a list of regions
|
|
177
171
|
if highlight is not None:
|
|
178
|
-
|
|
172
|
+
y_min = np.min(y)
|
|
173
|
+
y_max = np.max(y)
|
|
174
|
+
y_range = y_max - y_min
|
|
175
|
+
label_box_height = 0.20 * y_range
|
|
176
|
+
|
|
177
|
+
for i, highlight_region in enumerate(highlight):
|
|
179
178
|
if len(highlight_region) != 2:
|
|
180
|
-
raise excp.InputValueError(
|
|
179
|
+
raise excp.InputValueError("`highlight` should be a list of tuple of 2 values (left, right) => [(1, 10.5)]")
|
|
180
|
+
|
|
181
181
|
l, r = highlight_region
|
|
182
|
-
|
|
182
|
+
l = x[0] if l is None else l
|
|
183
|
+
r = x[-1] if r is None else r
|
|
184
|
+
|
|
185
|
+
# Highlight rectangle (main background)
|
|
186
|
+
ax.add_patch(Rectangle(
|
|
187
|
+
(l, y_min),
|
|
188
|
+
r - l,
|
|
189
|
+
y_range,
|
|
190
|
+
color='red',
|
|
191
|
+
alpha=0.2,
|
|
192
|
+
zorder=10
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
# Label box inside the top of the highlight
|
|
196
|
+
ax.add_patch(Rectangle(
|
|
197
|
+
(l, y_max - label_box_height),
|
|
198
|
+
r - l,
|
|
199
|
+
label_box_height,
|
|
200
|
+
color='red',
|
|
201
|
+
alpha=0.4,
|
|
202
|
+
zorder=11
|
|
203
|
+
))
|
|
204
|
+
|
|
205
|
+
# Centered label inside that box
|
|
206
|
+
ax.text(
|
|
207
|
+
(l + r) / 2,
|
|
208
|
+
y_max - label_box_height / 2,
|
|
209
|
+
str(i + 1),
|
|
210
|
+
ha='center',
|
|
211
|
+
va='center',
|
|
212
|
+
fontsize=10,
|
|
213
|
+
color='white',
|
|
214
|
+
fontweight='bold',
|
|
215
|
+
zorder=12
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Vertical lines
|
|
219
|
+
if vlines:
|
|
220
|
+
for xpos in vlines:
|
|
221
|
+
ax.axvline(x=xpos, color='blue', linestyle='--', linewidth=2, zorder=5)
|
|
222
|
+
|
|
223
|
+
# Horizontal lines
|
|
224
|
+
if hlines:
|
|
225
|
+
for ypos in hlines:
|
|
226
|
+
ax.axhline(y=ypos, color='blue', linestyle='--', linewidth=2, zorder=5)
|
|
227
|
+
|
|
228
|
+
# Show grid
|
|
229
|
+
if show_grid:
|
|
230
|
+
ax.grid(True, color="gray", linestyle="--", linewidth=0.5)
|
|
183
231
|
|
|
184
232
|
# Show/Return the figure as per needed
|
|
185
233
|
if created_fig:
|
|
186
234
|
fig.tight_layout()
|
|
187
235
|
if Plotter._in_notebook():
|
|
236
|
+
plt.tight_layout()
|
|
188
237
|
plt.close(fig)
|
|
189
238
|
return fig
|
|
190
239
|
else:
|
|
240
|
+
plt.tight_layout()
|
|
191
241
|
plt.show()
|
|
192
242
|
return fig
|
|
193
243
|
|
|
@@ -197,7 +247,6 @@ class Plotter(ModusaIO):
|
|
|
197
247
|
M: np.ndarray,
|
|
198
248
|
r: np.ndarray | None = None,
|
|
199
249
|
c: np.ndarray | None = None,
|
|
200
|
-
log_compression_factor: int | float | None = None,
|
|
201
250
|
ax: plt.Axes | None = None,
|
|
202
251
|
cmap: str = "gray_r",
|
|
203
252
|
title: str | None = None,
|
|
@@ -207,7 +256,10 @@ class Plotter(ModusaIO):
|
|
|
207
256
|
rlim: tuple[float, float] | None = None,
|
|
208
257
|
clim: tuple[float, float] | None = None,
|
|
209
258
|
highlight: list[tuple[float, float, float, float]] | None = None,
|
|
259
|
+
vlines: list[float] | None = None,
|
|
260
|
+
hlines: list[float] | None = None,
|
|
210
261
|
origin: str = "lower", # or "lower"
|
|
262
|
+
gamma: int | float | None = None,
|
|
211
263
|
show_colorbar: bool = True,
|
|
212
264
|
cax: plt.Axes | None = None,
|
|
213
265
|
show_grid: bool = True,
|
|
@@ -307,8 +359,8 @@ class Plotter(ModusaIO):
|
|
|
307
359
|
raise excp.InputValueError(f"`c` must have shape as `M column` not {c.shape}")
|
|
308
360
|
|
|
309
361
|
# Scale the signal if needed
|
|
310
|
-
if
|
|
311
|
-
M = np.log1p(float(
|
|
362
|
+
if gamma is not None:
|
|
363
|
+
M = np.log1p(float(gamma) * M)
|
|
312
364
|
|
|
313
365
|
# Create a figure
|
|
314
366
|
if ax is None:
|
|
@@ -377,14 +429,65 @@ class Plotter(ModusaIO):
|
|
|
377
429
|
if clim is not None:
|
|
378
430
|
ax.set_xlim(clim)
|
|
379
431
|
|
|
380
|
-
# Highlight a list of regions
|
|
381
432
|
if highlight is not None:
|
|
382
|
-
|
|
433
|
+
row_range = r.max() - r.min()
|
|
434
|
+
label_box_height = 0.08 * row_range
|
|
435
|
+
|
|
436
|
+
for i, highlight_region in enumerate(highlight):
|
|
437
|
+
if len(highlight_region) != 4 and len(highlight_region) != 2:
|
|
438
|
+
raise excp.InputValueError(
|
|
439
|
+
"`highlight` should be a list of tuple of 4 or 2 values (row_min, row_max, col_min, col_max) or (col_min, col_max) => [(1, 10.5, 2, 40)] or [(2, 40)] "
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if len(highlight_region) == 2:
|
|
443
|
+
r1, r2 = None, None
|
|
444
|
+
c1, c2 = highlight_region
|
|
445
|
+
elif len(highlight_region) == 4:
|
|
446
|
+
r1, r2, c1, c2 = highlight_region
|
|
447
|
+
|
|
448
|
+
r1 = r[0] if r1 is None else r1
|
|
449
|
+
r2 = r[-1] if r2 is None else r2
|
|
450
|
+
c1 = c[0] if c1 is None else c1
|
|
451
|
+
c2 = c[-1] if c2 is None else c2
|
|
452
|
+
|
|
383
453
|
row_min, row_max = min(r1, r2), max(r1, r2)
|
|
384
454
|
col_min, col_max = min(c1, c2), max(c1, c2)
|
|
455
|
+
|
|
385
456
|
width = col_max - col_min
|
|
386
457
|
height = row_max - row_min
|
|
387
|
-
|
|
458
|
+
|
|
459
|
+
# Main red highlight box
|
|
460
|
+
ax.add_patch(Rectangle(
|
|
461
|
+
(col_min, row_min),
|
|
462
|
+
width,
|
|
463
|
+
height,
|
|
464
|
+
color='red',
|
|
465
|
+
alpha=0.2,
|
|
466
|
+
zorder=10
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
# Label box inside top of highlight (just below row_max)
|
|
470
|
+
ax.add_patch(Rectangle(
|
|
471
|
+
(col_min, row_max - label_box_height),
|
|
472
|
+
width,
|
|
473
|
+
label_box_height,
|
|
474
|
+
color='red',
|
|
475
|
+
alpha=0.4,
|
|
476
|
+
zorder=11
|
|
477
|
+
))
|
|
478
|
+
|
|
479
|
+
# Centered label in that box
|
|
480
|
+
ax.text(
|
|
481
|
+
(col_min + col_max) / 2,
|
|
482
|
+
row_max - (label_box_height / 2),
|
|
483
|
+
str(i + 1),
|
|
484
|
+
ha='center',
|
|
485
|
+
va='center',
|
|
486
|
+
fontsize=10,
|
|
487
|
+
color='white',
|
|
488
|
+
fontweight='bold',
|
|
489
|
+
zorder=12
|
|
490
|
+
)
|
|
388
491
|
|
|
389
492
|
# Show colorbar
|
|
390
493
|
if show_colorbar is not None:
|
|
@@ -392,9 +495,19 @@ class Plotter(ModusaIO):
|
|
|
392
495
|
if Mlabel is not None:
|
|
393
496
|
cbar.set_label(Mlabel)
|
|
394
497
|
|
|
498
|
+
# Vertical lines
|
|
499
|
+
if vlines:
|
|
500
|
+
for xpos in vlines:
|
|
501
|
+
ax.axvline(x=xpos, color='blue', linestyle='--', linewidth=2, zorder=5)
|
|
502
|
+
|
|
503
|
+
# Horizontal lines
|
|
504
|
+
if hlines:
|
|
505
|
+
for ypos in hlines:
|
|
506
|
+
ax.axhline(y=ypos, color='blue', linestyle='--', linewidth=2, zorder=5)
|
|
507
|
+
|
|
395
508
|
# Show grid
|
|
396
509
|
if show_grid:
|
|
397
|
-
ax.grid(True, color="gray", linestyle="--", linewidth=0.5)
|
|
510
|
+
ax.grid(True, color="gray", linestyle="--", linewidth=0.5)
|
|
398
511
|
|
|
399
512
|
# Show/Return the figure as per needed
|
|
400
513
|
if created_fig:
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.
|
|
6
|
+
from modusa.tools.base import ModusaTool
|
|
7
7
|
from typing import Any
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
import yt_dlp
|
|
10
10
|
|
|
11
|
-
class YoutubeDownloader(
|
|
11
|
+
class YoutubeDownloader(ModusaTool):
|
|
12
12
|
"""
|
|
13
13
|
Download highest quality audio/video from YouTube.
|
|
14
14
|
|
modusa/utils/excp.py
CHANGED
|
@@ -4,73 +4,40 @@
|
|
|
4
4
|
#----------------------------------------
|
|
5
5
|
# Base class errors
|
|
6
6
|
#----------------------------------------
|
|
7
|
-
class
|
|
7
|
+
class ModusaBaseError(Exception):
|
|
8
8
|
"""
|
|
9
9
|
Ultimate base class for any kind of custom errors.
|
|
10
10
|
"""
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
|
-
class TypeError(
|
|
13
|
+
class TypeError(ModusaBaseError):
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
|
-
class InputError(
|
|
16
|
+
class InputError(ModusaBaseError):
|
|
17
17
|
"""
|
|
18
18
|
Any Input type error.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
class InputTypeError(
|
|
21
|
+
class InputTypeError(ModusaBaseError):
|
|
22
22
|
"""
|
|
23
23
|
Any Input type error.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
class InputValueError(
|
|
26
|
+
class InputValueError(ModusaBaseError):
|
|
27
27
|
"""
|
|
28
28
|
Any Input type error.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
class ImmutableAttributeError(
|
|
31
|
+
class ImmutableAttributeError(ModusaBaseError):
|
|
32
32
|
"""Raised when attempting to modify an immutable attribute."""
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
|
-
class FileNotFoundError(
|
|
35
|
+
class FileNotFoundError(ModusaBaseError):
|
|
36
36
|
"""Raised when file does not exist."""
|
|
37
37
|
pass
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
class PluginInputError(MusaBaseError):
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
class PluginOutputError(MusaBaseError):
|
|
39
|
+
class PluginInputError(ModusaBaseError):
|
|
44
40
|
pass
|
|
45
|
-
|
|
46
|
-
|
|
47
41
|
|
|
48
|
-
class
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
class AttributeNotFoundError(MusaBaseError):
|
|
52
|
-
pass
|
|
53
|
-
|
|
54
|
-
class ParsingError(MusaBaseError):
|
|
55
|
-
"""
|
|
56
|
-
Base class for any parsing related issues
|
|
57
|
-
"""
|
|
58
|
-
pass
|
|
59
|
-
|
|
60
|
-
class ValidationError(MusaBaseError):
|
|
61
|
-
"""
|
|
62
|
-
Base class for all input validation error
|
|
63
|
-
"""
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
class GenerationError(MusaBaseError):
|
|
67
|
-
"""
|
|
68
|
-
Error when generation fails
|
|
69
|
-
"""
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
class FileLoadingError(MusaBaseError):
|
|
73
|
-
"""
|
|
74
|
-
Error loading a file
|
|
75
|
-
"""
|
|
42
|
+
class PluginOutputError(ModusaBaseError):
|
|
76
43
|
pass
|