modusa 0.1.0__py3-none-any.whl → 0.2.0__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 (48) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/decorators.py +5 -5
  3. modusa/devtools/generate_template.py +80 -15
  4. modusa/devtools/main.py +6 -4
  5. modusa/devtools/templates/{engines.py → engine.py} +8 -7
  6. modusa/devtools/templates/{generators.py → generator.py} +8 -10
  7. modusa/devtools/templates/io.py +24 -0
  8. modusa/devtools/templates/{plugins.py → plugin.py} +7 -6
  9. modusa/devtools/templates/signal.py +40 -0
  10. modusa/devtools/templates/test.py +11 -0
  11. modusa/engines/.DS_Store +0 -0
  12. modusa/engines/__init__.py +1 -2
  13. modusa/generators/__init__.py +3 -1
  14. modusa/generators/audio_waveforms.py +227 -0
  15. modusa/generators/base.py +14 -25
  16. modusa/io/__init__.py +9 -0
  17. modusa/io/audio_converter.py +76 -0
  18. modusa/io/audio_loader.py +212 -0
  19. modusa/io/audio_player.py +72 -0
  20. modusa/io/base.py +43 -0
  21. modusa/io/plotter.py +430 -0
  22. modusa/io/youtube_downloader.py +139 -0
  23. modusa/main.py +15 -17
  24. modusa/plugins/__init__.py +1 -7
  25. modusa/signals/__init__.py +4 -6
  26. modusa/signals/audio_signal.py +421 -175
  27. modusa/signals/base.py +11 -271
  28. modusa/signals/frequency_domain_signal.py +329 -0
  29. modusa/signals/signal_ops.py +158 -0
  30. modusa/signals/spectrogram.py +465 -0
  31. modusa/signals/time_domain_signal.py +309 -0
  32. modusa/utils/excp.py +5 -0
  33. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/METADATA +15 -10
  34. modusa-0.2.0.dist-info/RECORD +47 -0
  35. modusa/devtools/templates/signals.py +0 -63
  36. modusa/engines/plot_1dsignal.py +0 -130
  37. modusa/engines/plot_2dmatrix.py +0 -159
  38. modusa/generators/basic_waveform.py +0 -185
  39. modusa/plugins/plot_1dsignal.py +0 -59
  40. modusa/plugins/plot_2dmatrix.py +0 -76
  41. modusa/plugins/plot_time_domain_signal.py +0 -59
  42. modusa/signals/signal1d.py +0 -311
  43. modusa/signals/signal2d.py +0 -226
  44. modusa/signals/uniform_time_domain_signal.py +0 -212
  45. modusa-0.1.0.dist-info/RECORD +0 -41
  46. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/WHEEL +0 -0
  47. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/entry_points.txt +0 -0
  48. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa import excp
5
- from modusa.decorators import validate_args_type
6
- from modusa.engines.base import ModusaEngine
7
- from typing import Any
8
- import numpy as np
9
- import matplotlib.pyplot as plt
10
- from matplotlib.patches import Rectangle
11
- from matplotlib.ticker import MaxNLocator
12
-
13
- class Plot2DMatrixEngine(ModusaEngine):
14
- """
15
-
16
- """
17
-
18
- #--------Meta Information----------
19
- name = "Plot 2D Matrix"
20
- description = ""
21
- author_name = "Ankit Anand"
22
- author_email = "ankit0.anand0@gmail.com"
23
- created_at = "2025-07-02"
24
- #----------------------------------
25
-
26
- def __init__(self):
27
- super().__init__()
28
-
29
-
30
- @staticmethod
31
- def compute_centered_extent(r: np.ndarray, c: np.ndarray, origin: str) -> list[float]:
32
- """
33
-
34
- """
35
- dc = np.diff(c).mean() if len(c) > 1 else 1
36
- dr = np.diff(r).mean() if len(r) > 1 else 1
37
- left = c[0] - dc / 2
38
- right = c[-1] + dc / 2
39
- bottom = r[0] - dr / 2
40
- top = r[-1] + dr / 2
41
- return [left, right, top, bottom] if origin == "upper" else [left, right, bottom, top]
42
-
43
-
44
- @validate_args_type()
45
- def run(
46
- self,
47
- M: np.ndarray,
48
- r: np.ndarray,
49
- c: np.ndarray,
50
- log_compression_factor: int | float | None,
51
- ax: plt.Axes | None,
52
- labels: tuple[str, str, str, str] | None,
53
- zoom: tuple[float, float, float, float] | None, # (r1, r2, c1, c2)
54
- highlight: list[tuple[float, float, float, float]] | None,
55
- cmap: str,
56
- origin: str, # or "lower"
57
- show_colorbar: bool,
58
- cax: plt.Axes | None,
59
- show_grid: bool,
60
- tick_mode: str, # "center" or "edge"
61
- n_ticks: tuple[int, int],
62
- value_range: tuple[float, float] | None,
63
- ) -> plt.Figure:
64
- pass
65
-
66
- # Validate the important args and get the signal that needs to be plotted
67
- if M.ndim != 2:
68
- raise excp.InputValueError(f"`M` must have 2 dimension not {M.ndim}")
69
- if r.ndim != 1 and c.ndim != 1:
70
- raise excp.InputValueError(f"`r` and `c` must have 2 dimension not r:{r.ndim}, c:{c.ndim}")
71
-
72
- if r.shape[0] != M.shape[0]:
73
- raise excp.InputValueError(f"`r` must have shape as `M row` not {r.shape}")
74
- if c.shape[0] != M.shape[1]:
75
- raise excp.InputValueError(f"`c` must have shape as `M column` not {c.shape}")
76
-
77
- # Scale the signal if needed
78
- if log_compression_factor is not None:
79
- M = np.log1p(float(log_compression_factor) * M)
80
-
81
- # Create a figure
82
- if ax is None:
83
- fig, ax = plt.subplots(figsize=(15, 4))
84
- created_fig = True
85
- else:
86
- fig = ax.get_figure()
87
- created_fig = False
88
-
89
- # Plot the signal with right configurations
90
- # Compute extent
91
- extent = Plot2DMatrixEngine.compute_centered_extent(r, c, origin)
92
-
93
- # Plot image
94
- im = ax.imshow(
95
- M,
96
- aspect="auto",
97
- cmap=cmap,
98
- origin=origin,
99
- extent=extent,
100
- vmin=value_range[0] if value_range else None,
101
- vmax=value_range[1] if value_range else None,
102
- )
103
-
104
- # Set the ticks and labels
105
- if tick_mode == "center":
106
- ax.yaxis.set_major_locator(MaxNLocator(nbins=n_ticks[0]))
107
- ax.xaxis.set_major_locator(MaxNLocator(nbins=n_ticks[1])) # limits ticks
108
-
109
- elif tick_mode == "edge":
110
- dc = np.diff(c).mean() if len(c) > 1 else 1
111
- dr = np.diff(r).mean() if len(r) > 1 else 1
112
- ax.set_xticks(np.append(c, c[-1] + dc) - dc / 2)
113
- ax.set_yticks(np.append(r, r[-1] + dr) - dr / 2)
114
-
115
- if labels is not None:
116
- if len(labels) > 0:
117
- ax.set_title(labels[0])
118
- if len(labels) > 2:
119
- ax.set_ylabel(labels[2])
120
- if len(labels) > 3:
121
- ax.set_xlabel(labels[3])
122
-
123
- # Zoom into a region
124
- if zoom is not None:
125
- r1, r2, c1, c2 = zoom
126
- ax.set_xlim(min(c1, c2), max(c1, c2))
127
- ax.set_ylim(
128
- (min(r1, r2), max(r1, r2)) if origin == "lower" else (max(r1, r2), min(r1, r2))
129
- )
130
-
131
- # Highlight a list of regions
132
- if highlight is not None:
133
- for r1, r2, c1, c2 in highlight:
134
- row_min, row_max = min(r1, r2), max(r1, r2)
135
- col_min, col_max = min(c1, c2), max(c1, c2)
136
- width = col_max - col_min
137
- height = row_max - row_min
138
- ax.add_patch(Rectangle((col_min, row_min), width, height, color='red', alpha=0.2, zorder=10))
139
-
140
- # Show colorbar
141
- if show_colorbar is not None:
142
- cbar = fig.colorbar(im, ax=ax, cax=cax)
143
- if len(labels) > 1:
144
- cbar.set_label(labels[1])
145
-
146
- # Show grid
147
- if show_grid:
148
- ax.grid(True, color="gray", linestyle="--", linewidth=0.5) # TODO
149
-
150
- # Show/Return the figure as per needed
151
- if created_fig:
152
- fig.tight_layout()
153
- try:
154
- get_ipython
155
- plt.close(fig)
156
- return fig
157
- except NameError:
158
- plt.show()
159
- return fig
@@ -1,185 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa.decorators import validate_args_type
5
- from modusa.generators.base import ModusaGenerator
6
- from modusa.signals import UniformTimeDomainSignal, AudioSignal
7
- from typing import Any
8
- import numpy as np
9
-
10
- class BasicWaveformGenerator(ModusaGenerator):
11
- """
12
-
13
- """
14
-
15
- #--------Meta Information----------
16
- name = ""
17
- description = ""
18
- author_name = "Ankit Anand"
19
- author_email = "ankit0.anand0@gmail.com"
20
- created_at = "2025-07-04"
21
- #----------------------------------
22
-
23
- def __init__(self, signal_cls: Any | None = None):
24
- if signal_cls is None:
25
- signal_cls = self.allowed_output_signal_types[0] # Automatically select the first signal
26
- super().__init__(signal_cls)
27
-
28
-
29
- @property
30
- def allowed_output_signal_types(self) -> tuple[type, ...]:
31
- return (UniformTimeDomainSignal, AudioSignal)
32
-
33
- #----------------------------
34
- # Generate functions
35
- #----------------------------
36
-
37
- def generate_example(self):
38
- """
39
- Generates an instance of `TimeDomainSignal` to test out the features quicky.
40
- """
41
-
42
- T = 0.01 # Time period
43
-
44
- t = np.arange(0, 10, T)
45
- y = np.sin(2 * np.pi * 10 * t)
46
- signal: UniformTimeDomainSignal | AudioSignal = self.signal_cls(y=y, t=t)
47
- signal.set_units(t_unit="sec")
48
- signal.set_name("Random")
49
- signal.set_plot_labels(title=signal.name, y_label="Amplitude", t_label="Time")
50
-
51
- return signal
52
-
53
- def generate_random(self):
54
- """
55
- Generates an instance of `TimeDomainSignal` with random initialisation.
56
- Good for testing purposes.
57
- """
58
-
59
- T = np.random.random()
60
-
61
- t = np.arange(0, np.random.randint(10, 40), T)
62
- y = np.random.random(size=t.shape[0])
63
- signal: UniformTimeDomainSignal | AudioSignal = self.signal_cls(y=y, t=t)
64
- signal.set_units(t_unit="sec")
65
- signal.set_name("Random")
66
- signal.set_plot_labels(title=signal.name, y_label="Amplitude", t_label="Time")
67
-
68
- return signal
69
-
70
- @validate_args_type()
71
- def generate_sinusoid(
72
- self,
73
- A: float | int = 1.0,
74
- f: float | int = 10.0,
75
- phi: float | int = 0.0,
76
- duration: float | int = 1.0,
77
- Ts: float | int = 0.01,
78
- ):
79
- """
80
- Generates a sinusoidal `TimeDomainSignal` with specified frequency, duration, sampling period, and phase.
81
- """
82
-
83
- A, f, phi, duration, Ts = float(A), float(f), float(phi), float(duration), float(Ts)
84
-
85
-
86
- t = np.arange(0, duration, Ts)
87
- y = A * np.sin(2 * np.pi * f * t + phi)
88
-
89
- signal = self.signal_cls(y=y, t=t)
90
- signal.set_name(name=f"Sinusoid: {f}Hz")
91
- signal.set_plot_labels(
92
- title=signal.name,
93
- y_label="Amplitude",
94
- t_label="Time"
95
- )
96
-
97
- return signal
98
-
99
-
100
- @validate_args_type()
101
- def generate_square(
102
- self,
103
- A: float | int = 1.0,
104
- f: float | int = 10.0,
105
- phi: float | int = 0.0,
106
- duration: float | int = 1.0,
107
- Ts: float | int = 0.01,
108
- ):
109
- """
110
- Generates a square wave
111
- """
112
-
113
- A, f, phi, duration, Ts = float(A), float(f), float(phi), float(duration), float(Ts)
114
-
115
- t = np.arange(0, duration, Ts)
116
- y = A * np.sign(np.sin(2 * np.pi * f * t + phi))
117
-
118
- signal: UniformTimeDomainSignal | AudioSignal = self.signal_cls(y=y, t=t)
119
- signal.set_units(t_unit="sec")
120
- signal.set_name(name=f"Square: {f}Hz")
121
- signal.set_plot_labels(
122
- title=signal.name,
123
- y_label="Amplitude",
124
- t_label="Time"
125
- )
126
-
127
- return signal
128
-
129
- @validate_args_type()
130
- def generate_sawtooth(
131
- self,
132
- A: float | int = 1.0,
133
- f: float | int = 10.0,
134
- phi: float | int = 0.0,
135
- duration: float | int = 1.0,
136
- Ts: float | int = 0.01,
137
- ):
138
- """
139
- Generates a sawtooth wave.
140
- """
141
-
142
- A, f, phi, duration, Ts = float(A), float(f), float(phi), float(duration), float(Ts)
143
-
144
- t = np.arange(0, duration, Ts)
145
- y = A * (2 * (t * f + phi) % 1 - 1)
146
-
147
- signal: UniformTimeDomainSignal | AudioSignal = self.signal_cls(y=y, t=t)
148
- signal.set_units(t_unit="sec")
149
- signal.set_name(name=f"Sawtooth: {f}Hz")
150
- signal.set_plot_labels(
151
- title=signal.name,
152
- y_label="Amplitude",
153
- t_label="Time"
154
- )
155
-
156
- return signal
157
-
158
- @validate_args_type()
159
- def generate_triangle(
160
- self,
161
- A: float | int = 1.0,
162
- f: float | int = 10.0,
163
- phi: float | int = 0.0,
164
- duration: float | int = 1.0,
165
- Ts: float | int = 0.01,
166
- ):
167
- """
168
- Generates a triangle wave
169
- """
170
-
171
- A, f, phi, duration, Ts = float(A), float(f), float(phi), float(duration), float(Ts)
172
-
173
- t = np.arange(0, duration, Ts)
174
- signal = A * np.abs(2 * (t * f + phi) % 1 - 1)
175
-
176
- signal: UniformTimeDomainSignal | AudioSignal = self.signal_cls(y=y)
177
- signal.set_units(t_unit="sec")
178
- signal.set_name(name=f"Triangle: {f}Hz")
179
- signal.set_plot_labels(
180
- title=signal.name,
181
- y_label="Amplitude",
182
- t_label="Time"
183
- )
184
-
185
- return signal
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa.plugins.base import ModusaPlugin
5
- from modusa.decorators import immutable_property, validate_args_type, plugin_safety_check
6
- import matplotlib.pyplot as plt
7
-
8
- class Plot1DSignalPlugin(ModusaPlugin):
9
- """
10
-
11
- """
12
-
13
- #--------Meta Information----------
14
- name = "Plot 1D Signal Plugin"
15
- description = "A 1D signal plotter plugin with various features."
16
- author_name = "Ankit Anand"
17
- author_email = "ankit0.anand0@gmail.com"
18
- created_at = "2025-07-02"
19
- #----------------------------------
20
-
21
- def __init__(self):
22
- super().__init__()
23
-
24
- @immutable_property(error_msg="Mutation not allowed.")
25
- def allowed_input_signal_types(self) -> tuple[type, ...]:
26
- from modusa.signals import Signal1D
27
- return (Signal1D, )
28
-
29
-
30
- @immutable_property(error_msg="Mutation not allowed.")
31
- def allowed_output_signal_types(self) -> tuple[type, ...]:
32
- return (plt.Figure, type(None)) # None is returned when we want to plot on a given axes, no figure is created in that case
33
-
34
-
35
- @plugin_safety_check()
36
- @validate_args_type()
37
- def apply(
38
- self,
39
- signal: "Signal1D",
40
- scale_y: tuple[float, float] | None = None,
41
- scale_x: tuple[float, float] | None = None,
42
- ax: plt.Axes | None = None,
43
- color: str | None = "k",
44
- marker: str | None = None,
45
- linestyle: str | None = None,
46
- stem: bool = False,
47
- labels: tuple[str, str, str] | None = None,
48
- legend_loc: str | None = None,
49
- zoom: tuple[float, float] | None = None,
50
- highlight: list[tuple[float, float], ...] | None = None,
51
- show_grid: bool | None = False,
52
- ) -> plt.Figure:
53
-
54
- # Run the engine here
55
- from modusa.engines import Plot1DSignalEngine
56
-
57
- fig: plt.Figure | None = Plot1DSignalEngine().run(y=signal.data, x=signal.x, scale_y=scale_y, scale_x=scale_x, ax=ax, color=color, marker=marker, linestyle=linestyle, stem=stem, labels=labels, legend_loc=legend_loc, zoom=zoom, highlight=highlight)
58
-
59
- return fig
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa.plugins.base import ModusaPlugin
5
- from modusa.decorators import immutable_property, validate_args_type, plugin_safety_check
6
- import matplotlib.pyplot as plt
7
-
8
- class Plot2DMatrixPlugin(ModusaPlugin):
9
- """
10
-
11
- """
12
-
13
- #--------Meta Information----------
14
- name = "Plot 2D Matrix Plugin"
15
- description = "A 2D matrix plotter plugin with various features."
16
- author_name = "Ankit Anand"
17
- author_email = "ankit0.anand0@gmail.com"
18
- created_at = "2025-07-02"
19
- #----------------------------------
20
-
21
- def __init__(self):
22
- super().__init__()
23
-
24
- @immutable_property(error_msg="Mutation not allowed.")
25
- def allowed_input_signal_types(self) -> tuple[type, ...]:
26
- from modusa.signals import Signal2D
27
- return (Signal2D, )
28
-
29
-
30
- @immutable_property(error_msg="Mutation not allowed.")
31
- def allowed_output_signal_types(self) -> tuple[type, ...]:
32
- return (plt.Figure, type(None)) # None is returned when we want to plot on a given axes, no figure is created in that case
33
-
34
-
35
- @plugin_safety_check()
36
- @validate_args_type()
37
- def apply(self,
38
- signal: "Signal2D",
39
- log_compression_factor: int | float | None = None,
40
- ax: plt.Axes | None = None,
41
- labels: tuple[str, str, str, str] | None = None,
42
- zoom: tuple[float, float, float, float] | None = None, # (r1, r2, c1, c2)
43
- highlight: list[tuple[float, float, float, float]] | None = None,
44
- cmap: str = "gray_r",
45
- origin: str = "upper", # or "lower"
46
- show_colorbar: bool = True,
47
- cax: plt.Axes | None = None,
48
- show_grid: bool = False,
49
- tick_mode: str = "center", # or "edge"
50
- n_ticks: tuple[int, int] = (11, 21),
51
- value_range: tuple[float, float] | None = None,
52
-
53
- ) -> "Signal2D":
54
-
55
- # Run the engine here
56
- from modusa.engines import Plot2DMatrixEngine
57
-
58
- fig: plt.Figure = Plot2DMatrixEngine().run(
59
- M=signal.M,
60
- r=signal.r,
61
- c=signal.c,
62
- log_compression_factor=log_compression_factor,
63
- ax=ax,
64
- labels=signal.labels,
65
- zoom=zoom,
66
- highlight=highlight,
67
- cmap=cmap,
68
- origin=origin,
69
- show_colorbar=show_colorbar,
70
- cax=cax,
71
- show_grid=show_grid,
72
- tick_mode=tick_mode,
73
- n_ticks=n_ticks,
74
- value_range=value_range)
75
-
76
- return fig
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- from modusa.plugins.base import ModusaPlugin
5
- from modusa.decorators import immutable_property, validate_args_type, plugin_safety_check
6
- import matplotlib.pyplot as plt
7
-
8
- class PlotTimeDomainSignalPlugin(ModusaPlugin):
9
- """
10
-
11
- """
12
-
13
- #--------Meta Information----------
14
- name = ""
15
- description = ""
16
- author_name = "Ankit Anand"
17
- author_email = "ankit0.anand0@gmail.com"
18
- created_at = "2025-07-03"
19
- #----------------------------------
20
-
21
- def __init__(self):
22
- super().__init__()
23
-
24
- @immutable_property(error_msg="Mutation not allowed.")
25
- def allowed_input_signal_types(self) -> tuple[type, ...]:
26
- from modusa.signals import UniformTimeDomainSignal, AudioSignal
27
- return (UniformTimeDomainSignal, AudioSignal, )
28
-
29
-
30
- @immutable_property(error_msg="Mutation not allowed.")
31
- def allowed_output_signal_types(self) -> tuple[type, ...]:
32
- return (plt.Figure, type(None))
33
-
34
-
35
- @plugin_safety_check()
36
- @validate_args_type()
37
- def apply(
38
- self,
39
- signal: "UniformTimeDomainSignal",
40
- scale_y: tuple[float, float] | None = None,
41
- scale_t: tuple[float, float] | None = None,
42
- ax: plt.Axes | None = None,
43
- color: str | None = "b",
44
- marker: str | None = None,
45
- linestyle: str | None = None,
46
- stem: bool = False,
47
- labels: tuple[str, str, str] | None = None,
48
- legend_loc: str | None = None,
49
- zoom: tuple[float, float] | None = None,
50
- highlight: list[tuple[float, float], ...] | None = None,
51
- show_grid: bool | None = False,
52
- ) -> plt.Figure:
53
-
54
- # Run the engine here
55
- from modusa.engines import Plot1DSignalEngine
56
-
57
- fig: plt.Figure | None = Plot1DSignalEngine().run(y=signal.data, x=signal.t, scale_y=scale_y, scale_x=scale_t, ax=ax, color=color, marker=marker, linestyle=linestyle, stem=stem, labels=labels, legend_loc=legend_loc, zoom=zoom, highlight=highlight)
58
-
59
- return fig