modusa 0.3.2__tar.gz → 0.3.4.dev2__tar.gz
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-0.3.2 → modusa-0.3.4.dev2}/PKG-INFO +1 -1
- {modusa-0.3.2 → modusa-0.3.4.dev2}/pyproject.toml +1 -1
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/__init__.py +4 -3
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/generate_docs_source.py +6 -10
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/__init__.py +3 -2
- modusa-0.3.4.dev2/src/modusa/tools/ann_loader.py +38 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/plotter.py +288 -42
- modusa-0.3.2/src/modusa/main.py +0 -3
- modusa-0.3.2/src/modusa/tmp.py +0 -98
- {modusa-0.3.2 → modusa-0.3.4.dev2}/LICENSE.md +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/README.md +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/.DS_Store +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/config.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/decorators.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/generate_template.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/list_authors.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/list_plugins.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/main.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/generator.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/io.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/model.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/plugin.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/test.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/devtools/templates/tool.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/__init__.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/audio.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/audio_waveforms.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/base.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/ftds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/s1d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/s2d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/s_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/t_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/generators/tds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/__init__.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/audio.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/base.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/data.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/ftds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/s1d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/s2d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/s_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/t_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/tds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/plugins/__init__.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/plugins/base.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/audio_converter.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/audio_loader.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/audio_player.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/base.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/math_ops.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/tools/youtube_downloader.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/.DS_Store +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/__init__.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/config.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/excp.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/logger.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/np_func_cat.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/utils/plot.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/__init__.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/data/song1.mp3 +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/data/song1.wav +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/audio_waveform.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_audio.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_ftds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_s1d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_s2d.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_s_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_signal_generator.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_t_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_generators/test_tds.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_io/audio_player.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_io/plotter.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_models/test_data.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_models/test_t_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_plugins/youtube_audio_loader.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/frequency_domain_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/spectrogram.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_axis.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_feature_time_domain_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_frequency_time_domain_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_signal1D.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_signal2D.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_time_domain_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_u_ax.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/test_window_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_signals/time_domain_signal.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_tools/test_audio_converter.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_tools/test_fourier_tranform.py +0 -0
- {modusa-0.3.2 → modusa-0.3.4.dev2}/tests/test_tools/test_math_ops.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from modusa.utils import excp, config
|
|
2
2
|
|
|
3
3
|
#=====Giving access to plot functions to plot multiple signals.=====
|
|
4
|
-
from modusa.tools import plot1d, plot2d
|
|
4
|
+
from modusa.tools import plot1d, plot2d, plot_dist
|
|
5
|
+
#=====
|
|
6
|
+
|
|
5
7
|
from modusa.tools import play, convert
|
|
6
8
|
from modusa.tools import download
|
|
7
|
-
from modusa.tools import load
|
|
8
|
-
#=====
|
|
9
|
+
from modusa.tools import load, load_ann
|
|
@@ -9,17 +9,15 @@ from collections import defaultdict
|
|
|
9
9
|
# === Configuration ===
|
|
10
10
|
BASE_MODULES = [
|
|
11
11
|
'modusa.tools',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# 'modusa.io'
|
|
12
|
+
# 'modusa.models',
|
|
13
|
+
# 'modusa.generators',
|
|
14
|
+
# 'modusa.plugins',
|
|
16
15
|
]
|
|
17
16
|
OUTPUT_DIRS = [
|
|
18
17
|
Path('docs/source/tools'),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Path('docs/source/io')
|
|
18
|
+
# Path('docs/source/models'),
|
|
19
|
+
# Path('docs/source/generators'),
|
|
20
|
+
# Path('docs/source/plugins'),
|
|
23
21
|
]
|
|
24
22
|
|
|
25
23
|
# Ensure output directories exist
|
|
@@ -89,8 +87,6 @@ def generate_docs_source():
|
|
|
89
87
|
write_module_rst_file(module_path, class_list, output_dir)
|
|
90
88
|
|
|
91
89
|
section_name = base_module.split('.')[-1].capitalize()
|
|
92
|
-
if section_name == "Io":
|
|
93
|
-
section_name = "IO"
|
|
94
90
|
|
|
95
91
|
write_index_rst_file(module_class_map, output_dir, section_name=section_name)
|
|
96
92
|
print(f"✅ Documentation generated for {base_module} in {output_dir}")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
from .plotter import plot1d, plot2d
|
|
3
|
+
from .plotter import plot1d, plot2d, plot_dist
|
|
4
4
|
from .audio_player import play
|
|
5
5
|
from .audio_converter import convert
|
|
6
6
|
from .youtube_downloader import download
|
|
7
|
-
from .audio_loader import load
|
|
7
|
+
from .audio_loader import load
|
|
8
|
+
from .ann_loader import load_ann
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
#---------------------------------
|
|
4
|
+
# Author: Ankit Anand
|
|
5
|
+
# Date: 12/08/25
|
|
6
|
+
# Email: ankit0.anand0@gmail.com
|
|
7
|
+
#---------------------------------
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
def load_ann(path):
|
|
12
|
+
"""
|
|
13
|
+
Load annotation from audatity label text file.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
path: str
|
|
18
|
+
label text file path.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
list[tuple, ...]
|
|
23
|
+
- annotation data structure
|
|
24
|
+
- [(start, end, tag), ...]
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
if not isinstance(path, (str, Path)):
|
|
28
|
+
raise ValueError(f"`path` must be one of (str, Path), got {type(path)}")
|
|
29
|
+
|
|
30
|
+
ann = []
|
|
31
|
+
with open(str(path), "r") as f:
|
|
32
|
+
lines = [line.rstrip("\n") for line in f]
|
|
33
|
+
for line in lines:
|
|
34
|
+
start, end, tag = line.split("\t")
|
|
35
|
+
start, end = float(start), float(end)
|
|
36
|
+
ann.append((start, end, tag))
|
|
37
|
+
|
|
38
|
+
return ann
|
|
@@ -7,8 +7,27 @@ import matplotlib.gridspec as gridspec
|
|
|
7
7
|
from matplotlib.patches import Rectangle
|
|
8
8
|
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
9
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
|
+
|
|
10
29
|
#======== 1D ===========
|
|
11
|
-
def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylabel=None, title=None, legend=None):
|
|
30
|
+
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):
|
|
12
31
|
"""
|
|
13
32
|
Plots a 1D signal using matplotlib.
|
|
14
33
|
|
|
@@ -53,26 +72,40 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
|
|
|
53
72
|
legend : list[str] | None
|
|
54
73
|
- List of legend labels corresponding to each signal if plotting multiple lines.
|
|
55
74
|
- Default: None
|
|
75
|
+
fmt: list[str] | None
|
|
76
|
+
- linefmt for different line plots.
|
|
77
|
+
- Default: None
|
|
78
|
+
show_grid: bool
|
|
79
|
+
- If you want to show the grid.
|
|
80
|
+
- Default: False
|
|
81
|
+
show_stem: bool:
|
|
82
|
+
- If you want stem plot.
|
|
83
|
+
- Default: False
|
|
56
84
|
|
|
57
85
|
Returns
|
|
58
86
|
-------
|
|
59
87
|
plt.Figure
|
|
60
88
|
Matplolib figure.
|
|
61
89
|
"""
|
|
62
|
-
|
|
90
|
+
|
|
63
91
|
for arg in args:
|
|
64
92
|
if len(arg) not in [1, 2]: # 1 if it just provides values, 2 if it provided axis as well
|
|
65
93
|
raise ValueError(f"1D signal needs to have max 2 arrays (y, x) or simply (y, )")
|
|
94
|
+
|
|
66
95
|
if isinstance(legend, str): legend = (legend, )
|
|
67
|
-
|
|
68
96
|
if legend is not None:
|
|
69
97
|
if len(legend) < len(args):
|
|
70
|
-
raise ValueError(f"
|
|
98
|
+
raise ValueError(f"`legend` should be provided for each signal.")
|
|
99
|
+
|
|
100
|
+
if isinstance(fmt, str): fmt = [fmt]
|
|
101
|
+
if fmt is not None:
|
|
102
|
+
if len(fmt) < len(args):
|
|
103
|
+
raise ValueError(f"`fmt` should be provided for each signal.")
|
|
104
|
+
|
|
105
|
+
colors = plt.get_cmap('tab10').colors
|
|
71
106
|
|
|
72
107
|
fig = plt.figure(figsize=(16, 2))
|
|
73
108
|
gs = gridspec.GridSpec(2, 1, height_ratios=[0.2, 1])
|
|
74
|
-
|
|
75
|
-
colors = plt.get_cmap('tab10').colors
|
|
76
109
|
|
|
77
110
|
signal_ax = fig.add_subplot(gs[1, 0])
|
|
78
111
|
annotation_ax = fig.add_subplot(gs[0, 0], sharex=signal_ax)
|
|
@@ -83,41 +116,91 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
|
|
|
83
116
|
|
|
84
117
|
if ylim is not None:
|
|
85
118
|
signal_ax.set_ylim(ylim)
|
|
86
|
-
|
|
87
119
|
|
|
88
120
|
# Add signal plot
|
|
89
121
|
for i, signal in enumerate(args):
|
|
90
122
|
if len(signal) == 1:
|
|
91
123
|
y = signal[0]
|
|
124
|
+
x = np.arange(y.size)
|
|
92
125
|
if legend is not None:
|
|
93
|
-
|
|
126
|
+
if show_stem is True:
|
|
127
|
+
markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
|
|
128
|
+
markerline.set_color(colors[i])
|
|
129
|
+
stemlines.set_color(colors[i])
|
|
130
|
+
baseline.set_color("k")
|
|
131
|
+
else:
|
|
132
|
+
if fmt is not None:
|
|
133
|
+
signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
|
|
134
|
+
else:
|
|
135
|
+
signal_ax.plot(x, y, color=colors[i], label=legend[i])
|
|
94
136
|
else:
|
|
95
|
-
|
|
137
|
+
if show_stem is True:
|
|
138
|
+
markerline, stemlines, baseline = signal_ax.stem(x, y)
|
|
139
|
+
markerline.set_color(colors[i])
|
|
140
|
+
stemlines.set_color(colors[i])
|
|
141
|
+
baseline.set_color("k")
|
|
142
|
+
else:
|
|
143
|
+
if fmt is not None:
|
|
144
|
+
signal_ax.plot(x, y, fmt[i], markersize=4)
|
|
145
|
+
else:
|
|
146
|
+
signal_ax.plot(x, y, color=colors[i])
|
|
147
|
+
|
|
96
148
|
elif len(signal) == 2:
|
|
97
149
|
y, x = signal[0], signal[1]
|
|
98
150
|
if legend is not None:
|
|
99
|
-
|
|
151
|
+
if show_stem is True:
|
|
152
|
+
markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
|
|
153
|
+
markerline.set_color(colors[i])
|
|
154
|
+
stemlines.set_color(colors[i])
|
|
155
|
+
baseline.set_color("k")
|
|
156
|
+
else:
|
|
157
|
+
if fmt is not None:
|
|
158
|
+
signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
|
|
159
|
+
else:
|
|
160
|
+
signal_ax.plot(x, y, color=colors[i], label=legend[i])
|
|
100
161
|
else:
|
|
101
|
-
|
|
162
|
+
if show_stem is True:
|
|
163
|
+
markerline, stemlines, baseline = signal_ax.stem(x, y)
|
|
164
|
+
markerline.set_color(colors[i])
|
|
165
|
+
stemlines.set_color(colors[i])
|
|
166
|
+
baseline.set_color("k")
|
|
167
|
+
else:
|
|
168
|
+
if fmt is not None:
|
|
169
|
+
signal_ax.plot(x, y, fmt[i], markersize=4)
|
|
170
|
+
else:
|
|
171
|
+
signal_ax.plot(x, y, color=colors[i])
|
|
172
|
+
|
|
102
173
|
|
|
103
174
|
# Add annotations
|
|
104
175
|
if ann is not None:
|
|
105
|
-
annotation_ax.set_ylim(0, 1)
|
|
176
|
+
annotation_ax.set_ylim(0, 1) # For consistent layout
|
|
177
|
+
# Determine visible x-range
|
|
178
|
+
x_view_min = xlim[0] if xlim is not None else np.min(x)
|
|
179
|
+
x_view_max = xlim[1] if xlim is not None else np.max(x)
|
|
180
|
+
|
|
106
181
|
for i, (start, end, tag) in enumerate(ann):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
182
|
+
# We make sure that we only plot annotation that are within the x range of the current view
|
|
183
|
+
if start >= x_view_max or end <= x_view_min:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# Clip boundaries to xlim
|
|
187
|
+
start = max(start, x_view_min)
|
|
188
|
+
end = min(end, x_view_max)
|
|
113
189
|
|
|
114
190
|
color = colors[i % len(colors)]
|
|
115
191
|
width = end - start
|
|
116
192
|
rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
|
|
117
193
|
annotation_ax.add_patch(rect)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
194
|
+
|
|
195
|
+
text_obj = annotation_ax.text(
|
|
196
|
+
(start + end) / 2, 0.5, tag,
|
|
197
|
+
ha='center', va='center',
|
|
198
|
+
fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
text_obj.set_clip_path(rect)
|
|
202
|
+
|
|
203
|
+
|
|
121
204
|
# Add vlines
|
|
122
205
|
if events is not None:
|
|
123
206
|
for xpos in events:
|
|
@@ -130,7 +213,7 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
|
|
|
130
213
|
# Add legend
|
|
131
214
|
if legend is not None:
|
|
132
215
|
handles, labels = signal_ax.get_legend_handles_labels()
|
|
133
|
-
fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.2), ncol=len(legend), frameon=
|
|
216
|
+
fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.2), ncol=len(legend), frameon=True)
|
|
134
217
|
|
|
135
218
|
# Set title, labels
|
|
136
219
|
if title is not None:
|
|
@@ -139,8 +222,12 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
|
|
|
139
222
|
signal_ax.set_xlabel(xlabel)
|
|
140
223
|
if ylabel is not None:
|
|
141
224
|
signal_ax.set_ylabel(ylabel)
|
|
225
|
+
|
|
226
|
+
# Add grid to the plot
|
|
227
|
+
if show_grid is True:
|
|
228
|
+
signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
|
|
142
229
|
|
|
143
|
-
#
|
|
230
|
+
# Remove the boundaries and ticks from an axis
|
|
144
231
|
if ann is not None:
|
|
145
232
|
annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
|
|
146
233
|
else:
|
|
@@ -152,7 +239,7 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
|
|
|
152
239
|
return fig
|
|
153
240
|
|
|
154
241
|
#======== 2D ===========
|
|
155
|
-
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):
|
|
242
|
+
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):
|
|
156
243
|
"""
|
|
157
244
|
Plots a 2D matrix (e.g., spectrogram or heatmap) with optional annotations and events.
|
|
158
245
|
|
|
@@ -206,6 +293,9 @@ def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", M
|
|
|
206
293
|
- Adds a circular marker for the line.
|
|
207
294
|
- Default: False
|
|
208
295
|
- Useful to show the data points.
|
|
296
|
+
show_grid: bool
|
|
297
|
+
- If you want to show the grid.
|
|
298
|
+
- Default: False
|
|
209
299
|
|
|
210
300
|
Returns
|
|
211
301
|
-------
|
|
@@ -246,6 +336,8 @@ def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", M
|
|
|
246
336
|
if data.ndim == 1: # 1D
|
|
247
337
|
if len(signal) == 1: # It means that the axis was not passed
|
|
248
338
|
x = np.arange(data.shape[0])
|
|
339
|
+
else:
|
|
340
|
+
x = signal[1]
|
|
249
341
|
|
|
250
342
|
if lm is False:
|
|
251
343
|
if legend is not None:
|
|
@@ -267,36 +359,42 @@ def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", M
|
|
|
267
359
|
if len(signal) == 1: # It means that the axes were not passed
|
|
268
360
|
y = np.arange(M.shape[0])
|
|
269
361
|
x = np.arange(M.shape[1])
|
|
270
|
-
|
|
271
|
-
dy = y[1] - y[0]
|
|
272
|
-
extent=[x[0] - dx/2, x[-1] + dx/2, y[0] - dy/2, y[-1] + dy/2]
|
|
362
|
+
extent = _calculate_extent(x, y)
|
|
273
363
|
im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
|
|
274
364
|
|
|
275
365
|
elif len(signal) == 3: # It means that the axes were passed
|
|
276
366
|
M, y, x = signal[0], signal[1], signal[2]
|
|
277
|
-
|
|
278
|
-
dy = y[1] - y[0]
|
|
279
|
-
extent=[x[0] - dx/2, x[-1] + dx/2, y[0] - dy/2, y[-1] + dy/2]
|
|
367
|
+
extent = _calculate_extent(x, y)
|
|
280
368
|
im = signal_ax.imshow(M, aspect="auto", origin=origin, cmap="gray_r", extent=extent)
|
|
281
369
|
|
|
282
370
|
# Add annotations
|
|
283
371
|
if ann is not None:
|
|
284
|
-
annotation_ax.set_ylim(0, 1)
|
|
372
|
+
annotation_ax.set_ylim(0, 1) # For consistent layout
|
|
373
|
+
# Determine visible x-range
|
|
374
|
+
x_view_min = xlim[0] if xlim is not None else np.min(x)
|
|
375
|
+
x_view_max = xlim[1] if xlim is not None else np.max(x)
|
|
376
|
+
|
|
285
377
|
for i, (start, end, tag) in enumerate(ann):
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
378
|
+
# We make sure that we only plot annotation that are within the x range of the current view
|
|
379
|
+
if start >= x_view_max or end <= x_view_min:
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
# Clip boundaries to xlim
|
|
383
|
+
start = max(start, x_view_min)
|
|
384
|
+
end = min(end, x_view_max)
|
|
292
385
|
|
|
293
386
|
color = colors[i % len(colors)]
|
|
294
387
|
width = end - start
|
|
295
388
|
rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
|
|
296
389
|
annotation_ax.add_patch(rect)
|
|
297
|
-
annotation_ax.text(
|
|
298
|
-
|
|
299
|
-
|
|
390
|
+
text_obj = annotation_ax.text(
|
|
391
|
+
(start + end) / 2, 0.5, tag,
|
|
392
|
+
ha='center', va='center',
|
|
393
|
+
fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
text_obj.set_clip_path(rect)
|
|
397
|
+
|
|
300
398
|
# Add vlines
|
|
301
399
|
if events is not None:
|
|
302
400
|
for xpos in events:
|
|
@@ -338,7 +436,10 @@ def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", M
|
|
|
338
436
|
signal_ax.set_xlabel(xlabel)
|
|
339
437
|
if ylabel is not None:
|
|
340
438
|
signal_ax.set_ylabel(ylabel)
|
|
341
|
-
|
|
439
|
+
|
|
440
|
+
# Add grid to the plot
|
|
441
|
+
if show_grid is True:
|
|
442
|
+
signal_ax.grid(True, linestyle=':', linewidth=0.7, color='gray', alpha=0.7)
|
|
342
443
|
|
|
343
444
|
# Making annotation axis spines thicker
|
|
344
445
|
if ann is not None:
|
|
@@ -348,4 +449,149 @@ def plot2d(*args, ann=None, events=None, xlim=None, ylim=None, origin="lower", M
|
|
|
348
449
|
|
|
349
450
|
fig.subplots_adjust(hspace=0.01, wspace=0.05)
|
|
350
451
|
plt.close()
|
|
351
|
-
return fig
|
|
452
|
+
return fig
|
|
453
|
+
|
|
454
|
+
#======== Plot distribution ===========
|
|
455
|
+
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):
|
|
456
|
+
"""
|
|
457
|
+
Plot distribution.
|
|
458
|
+
|
|
459
|
+
.. code-block:: python
|
|
460
|
+
|
|
461
|
+
import modusa as ms
|
|
462
|
+
import numpy as np
|
|
463
|
+
np.random.seed(42)
|
|
464
|
+
data = np.random.normal(loc=1, scale=1, size=1000)
|
|
465
|
+
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")
|
|
466
|
+
|
|
467
|
+
Parameters
|
|
468
|
+
----------
|
|
469
|
+
*args: ndarray
|
|
470
|
+
- Data arrays for which distribution needs to be plotted.
|
|
471
|
+
- Arrays will be flattened.
|
|
472
|
+
ann : list[tuple[Number, Number, str] | None
|
|
473
|
+
- A list of annotations to mark specific points. Each tuple should be of the form (start, end, label).
|
|
474
|
+
- Default: None => No annotation.
|
|
475
|
+
events : list[Number] | None
|
|
476
|
+
- A list of x-values where vertical lines (event markers) will be drawn.
|
|
477
|
+
- Default: None
|
|
478
|
+
xlim : tuple[Number, Number] | None
|
|
479
|
+
- Limits for the x-axis as (xmin, xmax).
|
|
480
|
+
- Default: None
|
|
481
|
+
ylim : tuple[Number, Number] | None
|
|
482
|
+
- Limits for the y-axis as (ymin, ymax).
|
|
483
|
+
- Default: None
|
|
484
|
+
xlabel : str | None
|
|
485
|
+
- Label for the x-axis.
|
|
486
|
+
- - Default: None
|
|
487
|
+
ylabel : str | None
|
|
488
|
+
- Label for the y-axis.
|
|
489
|
+
- Default: None
|
|
490
|
+
title : str | None
|
|
491
|
+
- Title of the plot.
|
|
492
|
+
- Default: None
|
|
493
|
+
legend : list[str] | None
|
|
494
|
+
- List of legend labels corresponding to each signal if plotting multiple distributions.
|
|
495
|
+
- Default: None
|
|
496
|
+
show_hist: bool
|
|
497
|
+
- Want to show histogram as well.
|
|
498
|
+
npoints: int
|
|
499
|
+
- Number of points for which gaussian needs to be computed between min and max.
|
|
500
|
+
- Higher value means more points are evaluated with the fitted gaussian, thereby higher resolution.
|
|
501
|
+
bins: int
|
|
502
|
+
- The number of bins for histogram.
|
|
503
|
+
- This is used only to plot the histogram.
|
|
504
|
+
|
|
505
|
+
Returns
|
|
506
|
+
-------
|
|
507
|
+
plt.Figure
|
|
508
|
+
- Matplotlib figure.
|
|
509
|
+
"""
|
|
510
|
+
from scipy.stats import gaussian_kde
|
|
511
|
+
|
|
512
|
+
if isinstance(legend, str):
|
|
513
|
+
legend = (legend, )
|
|
514
|
+
|
|
515
|
+
if legend is not None:
|
|
516
|
+
if len(legend) < len(args):
|
|
517
|
+
raise ValueError(f"Legend should be provided for each signal.")
|
|
518
|
+
|
|
519
|
+
# Create figure
|
|
520
|
+
fig = plt.figure(figsize=(16, 4))
|
|
521
|
+
gs = gridspec.GridSpec(2, 1, height_ratios=[0.1, 1])
|
|
522
|
+
|
|
523
|
+
colors = plt.get_cmap('tab10').colors
|
|
524
|
+
|
|
525
|
+
dist_ax = fig.add_subplot(gs[1, 0])
|
|
526
|
+
annotation_ax = fig.add_subplot(gs[0, 0], sharex=dist_ax)
|
|
527
|
+
|
|
528
|
+
# Set limits
|
|
529
|
+
if xlim is not None:
|
|
530
|
+
dist_ax.set_xlim(xlim)
|
|
531
|
+
|
|
532
|
+
if ylim is not None:
|
|
533
|
+
dist_ax.set_ylim(ylim)
|
|
534
|
+
|
|
535
|
+
# Add plot
|
|
536
|
+
for i, data in enumerate(args):
|
|
537
|
+
# Fit gaussian to the data
|
|
538
|
+
kde = gaussian_kde(data)
|
|
539
|
+
|
|
540
|
+
# Create points to evaluate KDE
|
|
541
|
+
x = np.linspace(np.min(data), np.max(data), npoints)
|
|
542
|
+
y = kde(x)
|
|
543
|
+
|
|
544
|
+
if legend is not None:
|
|
545
|
+
dist_ax.plot(x, y, color=colors[i], label=legend[i])
|
|
546
|
+
if show_hist is True:
|
|
547
|
+
dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black', label=legend[i])
|
|
548
|
+
else:
|
|
549
|
+
dist_ax.plot(x, y, color=colors[i])
|
|
550
|
+
if show_hist is True:
|
|
551
|
+
dist_ax.hist(data, bins=bins, density=True, alpha=0.3, facecolor=colors[i], edgecolor='black')
|
|
552
|
+
|
|
553
|
+
# Add annotations
|
|
554
|
+
if ann is not None:
|
|
555
|
+
annotation_ax.set_ylim(0, 1) # For consistent layout
|
|
556
|
+
# Determine visible x-range
|
|
557
|
+
x_view_min = xlim[0] if xlim is not None else np.min(x)
|
|
558
|
+
x_view_max = xlim[1] if xlim is not None else np.max(x)
|
|
559
|
+
for i, (start, end, tag) in enumerate(ann):
|
|
560
|
+
# We make sure that we only plot annotation that are within the x range of the current view
|
|
561
|
+
if start >= x_view_max or end <= x_view_min:
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
# Clip boundaries to xlim
|
|
565
|
+
start = max(start, x_view_min)
|
|
566
|
+
end = min(end, x_view_max)
|
|
567
|
+
|
|
568
|
+
color = colors[i % len(colors)]
|
|
569
|
+
width = end - start
|
|
570
|
+
rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
|
|
571
|
+
annotation_ax.add_patch(rect)
|
|
572
|
+
|
|
573
|
+
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)
|
|
574
|
+
text_obj.set_clip_path(rect)
|
|
575
|
+
|
|
576
|
+
# Add legend
|
|
577
|
+
if legend is not None:
|
|
578
|
+
handles, labels = dist_ax.get_legend_handles_labels()
|
|
579
|
+
fig.legend(handles, labels, loc='upper right', bbox_to_anchor=(0.9, 1.1), ncol=len(legend), frameon=True)
|
|
580
|
+
|
|
581
|
+
# Set title, labels
|
|
582
|
+
if title is not None:
|
|
583
|
+
annotation_ax.set_title(title, pad=10, size=11)
|
|
584
|
+
if xlabel is not None:
|
|
585
|
+
dist_ax.set_xlabel(xlabel)
|
|
586
|
+
if ylabel is not None:
|
|
587
|
+
dist_ax.set_ylabel(ylabel)
|
|
588
|
+
|
|
589
|
+
# Remove the boundaries and ticks from annotation axis
|
|
590
|
+
if ann is not None:
|
|
591
|
+
annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
|
|
592
|
+
else:
|
|
593
|
+
annotation_ax.axis("off")
|
|
594
|
+
|
|
595
|
+
fig.subplots_adjust(hspace=0.01, wspace=0.05)
|
|
596
|
+
plt.close()
|
|
597
|
+
return fig
|
modusa-0.3.2/src/modusa/main.py
DELETED
modusa-0.3.2/src/modusa/tmp.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#def autocorr(self) -> Self:
|
|
2
|
-
# """
|
|
3
|
-
#
|
|
4
|
-
# """
|
|
5
|
-
# raise NotImplementedError
|
|
6
|
-
# r = np.correlate(self.data, self.data, mode="full")
|
|
7
|
-
# r = r[self.data.shape[0] - 1:]
|
|
8
|
-
# r_signal = self.__class__(data=r, sr=self.sr, t0=self.t0, title=self.title + " [Autocorr]")
|
|
9
|
-
# return r_signal
|
|
10
|
-
|
|
11
|
-
# #----------------------------
|
|
12
|
-
# # To different signals
|
|
13
|
-
# #----------------------------
|
|
14
|
-
# def to_audio_signal(self) -> "AudioSignal":
|
|
15
|
-
# """
|
|
16
|
-
# Moves TimeDomainSignal to AudioSignal
|
|
17
|
-
# """
|
|
18
|
-
# raise NotImplementedError
|
|
19
|
-
# from modusa.signals.audio_signal import AudioSignal
|
|
20
|
-
#
|
|
21
|
-
# return AudioSignal(data=self.data, sr=self.sr, t0=self.t0, title=self.title)
|
|
22
|
-
#
|
|
23
|
-
# def to_spectrogram(
|
|
24
|
-
# self,
|
|
25
|
-
# n_fft: int = 2048,
|
|
26
|
-
# hop_length: int = 512,
|
|
27
|
-
# win_length: int | None = None,
|
|
28
|
-
# window: str = "hann"
|
|
29
|
-
# ) -> "Spectrogram":
|
|
30
|
-
# """
|
|
31
|
-
# Compute the Short-Time Fourier Transform (STFT) and return a Spectrogram object.
|
|
32
|
-
#
|
|
33
|
-
# Parameters
|
|
34
|
-
# ----------
|
|
35
|
-
# n_fft : int
|
|
36
|
-
# FFT size.
|
|
37
|
-
# win_length : int or None
|
|
38
|
-
# Window length. Defaults to `n_fft` if None.
|
|
39
|
-
# hop_length : int
|
|
40
|
-
# Hop length between frames.
|
|
41
|
-
# window : str
|
|
42
|
-
# Type of window function to use (e.g., 'hann', 'hamming').
|
|
43
|
-
#
|
|
44
|
-
# Returns
|
|
45
|
-
# -------
|
|
46
|
-
# Spectrogram
|
|
47
|
-
# Spectrogram object containing S (complex STFT), t (time bins), and f (frequency bins).
|
|
48
|
-
# """
|
|
49
|
-
# raise NotImplementedError
|
|
50
|
-
# import warnings
|
|
51
|
-
# warnings.filterwarnings("ignore", category=UserWarning, module="librosa.core.intervals")
|
|
52
|
-
#
|
|
53
|
-
# from modusa.signals.feature_time_domain_signal import FeatureTimeDomainSignal
|
|
54
|
-
# import librosa
|
|
55
|
-
#
|
|
56
|
-
# S = librosa.stft(self.data, n_fft=n_fft, win_length=win_length, hop_length=hop_length, window=window)
|
|
57
|
-
# f = librosa.fft_frequencies(sr=self.sr, n_fft=n_fft)
|
|
58
|
-
# t = librosa.frames_to_time(np.arange(S.shape[1]), sr=self.sr, hop_length=hop_length)
|
|
59
|
-
# frame_rate = self.sr / hop_length
|
|
60
|
-
# spec = FeatureTimeDomainSignal(data=S, feature=f, feature_label="Freq (Hz)", frame_rate=frame_rate, t0=self.t0, time_label="Time (sec)", title=self.title)
|
|
61
|
-
# if self.title != self._name: # Means title of the audio was reset so we pass that info to spec
|
|
62
|
-
# spec = spec.set_meta_info(title=self.title)
|
|
63
|
-
#
|
|
64
|
-
# return spec
|
|
65
|
-
# #=====================================
|
|
66
|
-
|
|
67
|
-
#=====================================
|
|
68
|
-
|
|
69
|
-
#--------------------------
|
|
70
|
-
# Other signal ops
|
|
71
|
-
#--------------------------
|
|
72
|
-
|
|
73
|
-
# def interpolate(self, to: TimeDomainSignal, kind: str = "linear", fill_value: str | float = "extrapolate") -> TimeDomainSignal:
|
|
74
|
-
# """
|
|
75
|
-
# Interpolate the current signal to match the time axis of `to`.
|
|
76
|
-
#
|
|
77
|
-
# Parameters:
|
|
78
|
-
# to (TimeDomainSignal): The signal whose time axis will be used.
|
|
79
|
-
# kind (str): Interpolation method ('linear', 'nearest', etc.)
|
|
80
|
-
# fill_value (str or float): Value used to fill out-of-bounds.
|
|
81
|
-
#
|
|
82
|
-
# Returns:
|
|
83
|
-
# TimeDomainSignal: A new signal with values interpolated at `to.t`.
|
|
84
|
-
# """
|
|
85
|
-
# assert self.y.ndim == 1, "Only 1D signals supported for interpolation"
|
|
86
|
-
#
|
|
87
|
-
# interpolator = interp1d(
|
|
88
|
-
# self.t,
|
|
89
|
-
# self.y,
|
|
90
|
-
# kind=kind,
|
|
91
|
-
# fill_value=fill_value,
|
|
92
|
-
# bounds_error=False,
|
|
93
|
-
# assume_sorted=True
|
|
94
|
-
# )
|
|
95
|
-
#
|
|
96
|
-
# y_interp = interpolator(to.y)
|
|
97
|
-
|
|
98
|
-
# return self.__class__(y=y_interp, sr=to.sr, t0=to.t0, title=f"{self.title} → interpolated")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modusa-0.3.2 → modusa-0.3.4.dev2}/src/modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|