modusa 0.4.28__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/synth.py +2 -0
- modusa/tools/youtube_downloader.py +1 -4
- {modusa-0.4.28.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.28.dist-info/RECORD +0 -65
- {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/WHEEL +0 -0
- {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/entry_points.txt +0 -0
- {modusa-0.4.28.dist-info → modusa-0.4.30.dist-info}/licenses/LICENSE.md +0 -0
modusa/models/s2d.py
DELETED
@@ -1,619 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
|
4
|
-
from modusa import excp
|
5
|
-
from modusa.decorators import immutable_property, validate_args_type
|
6
|
-
from .base import ModusaSignal
|
7
|
-
from .s_ax import SAx
|
8
|
-
from .data import Data
|
9
|
-
from modusa.tools.math_ops import MathOps
|
10
|
-
from typing import Self, Any, Callable
|
11
|
-
from types import NoneType
|
12
|
-
import numpy as np
|
13
|
-
import matplotlib.pyplot as plt
|
14
|
-
import copy
|
15
|
-
|
16
|
-
class S2D(ModusaSignal):
|
17
|
-
"""
|
18
|
-
Space to represent 2D signal.
|
19
|
-
|
20
|
-
Note
|
21
|
-
----
|
22
|
-
- Use :class:`~modusa.generators.s2d.S2DGen` API to instantiate this class.
|
23
|
-
- The signal can have uniform/non-uniform axes.
|
24
|
-
|
25
|
-
Parameters
|
26
|
-
----------
|
27
|
-
M: Data
|
28
|
-
- Data object holding the main 2D array.
|
29
|
-
y: SAx
|
30
|
-
- Y-axis of the signal.
|
31
|
-
x: SAx
|
32
|
-
- X-axis of the signal.
|
33
|
-
title: str
|
34
|
-
- What does the signal represent?
|
35
|
-
- e.g. "MySignal"
|
36
|
-
- This is used as the title while plotting.
|
37
|
-
"""
|
38
|
-
|
39
|
-
#--------Meta Information----------
|
40
|
-
_name = "Signal 2D"
|
41
|
-
_nickname = "signal" # This is to be used in repr/str methods
|
42
|
-
_description = "Space to represent 2D signal."
|
43
|
-
_author_name = "Ankit Anand"
|
44
|
-
_author_email = "ankit0.anand0@gmail.com"
|
45
|
-
_created_at = "2025-07-20"
|
46
|
-
#----------------------------------
|
47
|
-
|
48
|
-
def __init__(self, M, y, x, title = None):
|
49
|
-
super().__init__() # Instantiating `ModusaSignal` class
|
50
|
-
|
51
|
-
if not (isinstance(M, Data) and isinstance(y, SAx), isinstance(x, SAx)):
|
52
|
-
raise TypeError(f"`M` must be `Data` instance, `y` and `x` must be `SAx` instances, got {type(M)}, {type(y)} and {type(x)}")
|
53
|
-
|
54
|
-
assert M.ndim == 2
|
55
|
-
assert M.shape[0] == y.shape[0], f"M and y shape mismatch"
|
56
|
-
assert M.shape[1] == x.shape[0], f"M and x shape mismatch"
|
57
|
-
|
58
|
-
# All these are private and we do not expose it to users directly.
|
59
|
-
self._M = M
|
60
|
-
self._y = y
|
61
|
-
self._x = x
|
62
|
-
self._title = title or self._name
|
63
|
-
|
64
|
-
#--------------------------------------
|
65
|
-
# Properties
|
66
|
-
#--------------------------------------
|
67
|
-
|
68
|
-
@property
|
69
|
-
def M(self) -> Data:
|
70
|
-
return self._M
|
71
|
-
|
72
|
-
@property
|
73
|
-
def y(self) -> SAx:
|
74
|
-
return self._y
|
75
|
-
|
76
|
-
@property
|
77
|
-
def x(self) -> SAx:
|
78
|
-
return self._x
|
79
|
-
|
80
|
-
@property
|
81
|
-
def title(self) -> str:
|
82
|
-
return self._title
|
83
|
-
|
84
|
-
@property
|
85
|
-
def shape(self) -> tuple:
|
86
|
-
return self.M.shape
|
87
|
-
|
88
|
-
@property
|
89
|
-
def ndim(self) -> tuple:
|
90
|
-
return self.M.ndim # Should be 2
|
91
|
-
|
92
|
-
@property
|
93
|
-
def size(self) -> int:
|
94
|
-
return self.M.size
|
95
|
-
|
96
|
-
#===============================
|
97
|
-
|
98
|
-
|
99
|
-
#-------------------------------
|
100
|
-
# NumPy Protocol
|
101
|
-
#-------------------------------
|
102
|
-
|
103
|
-
def __array__(self, dtype=None) -> np.ndarray:
|
104
|
-
return np.asarray(self.M.values, dtype=dtype)
|
105
|
-
|
106
|
-
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
107
|
-
"""
|
108
|
-
Supports NumPy universal functions on the Signal1D object.
|
109
|
-
"""
|
110
|
-
from .data import Data # Ensure this is the same Data class you're using
|
111
|
-
from modusa.utils import np_func_cat as nfc
|
112
|
-
|
113
|
-
raw_inputs = [
|
114
|
-
np.asarray(obj.M) if isinstance(obj, type(self)) else obj
|
115
|
-
for obj in inputs
|
116
|
-
]
|
117
|
-
|
118
|
-
result = getattr(ufunc, method)(*raw_inputs, **kwargs)
|
119
|
-
|
120
|
-
result = Data(values=result, label=None)
|
121
|
-
y = self.y.copy()
|
122
|
-
x = self.x.copy()
|
123
|
-
|
124
|
-
if result.shape[0] != y.shape[0] or result.shape[1] != x.shape[0]:
|
125
|
-
raise ValueError(f"`{ufunc.__name__}` caused shape mismatch between data and axis, please create a github issue")
|
126
|
-
|
127
|
-
return self.__class__(M=result, y=y, x=x, title=self.title)
|
128
|
-
|
129
|
-
def __array_function__(self, func, types, args, kwargs):
|
130
|
-
"""
|
131
|
-
Additional numpy function support for modusa signals.
|
132
|
-
Handles reduction and ufunc-like behavior.
|
133
|
-
"""
|
134
|
-
from .data import Data
|
135
|
-
from modusa.utils import np_func_cat as nfc
|
136
|
-
|
137
|
-
if not all(issubclass(t, type(self)) for t in types):
|
138
|
-
return NotImplemented
|
139
|
-
|
140
|
-
if func in nfc.CONCAT_FUNCS:
|
141
|
-
raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
|
142
|
-
|
143
|
-
signal = args[0]
|
144
|
-
result: Data = func(signal.M, **kwargs)
|
145
|
-
axis = kwargs.get("axis", None)
|
146
|
-
keepdims = kwargs.get("keepdims", None)
|
147
|
-
|
148
|
-
if func in nfc.REDUCTION_FUNCS:
|
149
|
-
if keepdims is None or keepdims is True:
|
150
|
-
if axis is None: # Both axes collapsed
|
151
|
-
dummy_y = SAx(0, label=None)
|
152
|
-
dummy_x = SAx(values=0, label=None)
|
153
|
-
return self.__class__(M=result, y=dummy_y, x=dummy_x, title=signal.title)
|
154
|
-
|
155
|
-
if isinstance(axis, int): # One of the axis collapsed
|
156
|
-
if axis == 0:
|
157
|
-
dummy_y = SAx(0, label=None)
|
158
|
-
return self.__class__(M=result, y=dummy_y, x=signal.x.copy(), title=signal.title)
|
159
|
-
elif axis in [1, -1]:
|
160
|
-
dummy_x = SAx(values=0, label=None)
|
161
|
-
return self.__class__(M=result, y=signal.y.copy(), x=dummy_x, title=signal.title)
|
162
|
-
else:
|
163
|
-
raise ValueError
|
164
|
-
elif keepdims is False:
|
165
|
-
if axis is None: # Return Data
|
166
|
-
return result
|
167
|
-
if axis == 0: # Return S1D
|
168
|
-
from .s1d import S1D
|
169
|
-
return S1D(y=result, x=signal.x, title=signal.title)
|
170
|
-
elif axis in [1, -1]: # Return S1D
|
171
|
-
from .s1d import S1D
|
172
|
-
return S1D(y=result, x=signal.y, title=signal.title)
|
173
|
-
else:
|
174
|
-
raise ValueError
|
175
|
-
|
176
|
-
elif func in nfc.X_NEEDS_ADJUSTMENT_FUNCS:
|
177
|
-
raise NotImplementedError(f"{func.__name__} requires x-axis adjustment logic.")
|
178
|
-
|
179
|
-
else:
|
180
|
-
raise NotImplementedError(f"`{func.__name__}` is not yet tested on modusa signal, please create a GitHub issue.")
|
181
|
-
|
182
|
-
|
183
|
-
#================================
|
184
|
-
|
185
|
-
#-------------------------------
|
186
|
-
# Indexing
|
187
|
-
#-------------------------------
|
188
|
-
|
189
|
-
def __getitem__(self, key):
|
190
|
-
"""
|
191
|
-
Return a sliced or indexed view of the data.
|
192
|
-
|
193
|
-
Parameters
|
194
|
-
----------
|
195
|
-
key : int | slice | S2D
|
196
|
-
- Index to apply to the values.
|
197
|
-
|
198
|
-
Returns
|
199
|
-
-------
|
200
|
-
S2D
|
201
|
-
- Another sliced S2D object
|
202
|
-
"""
|
203
|
-
|
204
|
-
if not isinstance(key, (int, slice, tuple)):
|
205
|
-
raise TypeError(f"Invalid key type {type(key)}")
|
206
|
-
|
207
|
-
# We slice the data
|
208
|
-
sliced_M = self.M[key]
|
209
|
-
|
210
|
-
if isinstance(key, (int, slice)):
|
211
|
-
sliced_y = self.y[key]
|
212
|
-
sliced_x = self.x
|
213
|
-
|
214
|
-
if isinstance(key, tuple):
|
215
|
-
sliced_y = self.y[key[0]]
|
216
|
-
sliced_x = self.x[key[1]]
|
217
|
-
|
218
|
-
return self.__class__(M=sliced_M, y=sliced_y, x=sliced_x, title=self.title)
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
def __setitem__(self, key, value):
|
223
|
-
"""
|
224
|
-
Set values at the specified index.
|
225
|
-
|
226
|
-
Parameters
|
227
|
-
----------
|
228
|
-
key : int | slice | array-like | boolean array | S1D
|
229
|
-
Index to apply to the values.
|
230
|
-
value : int | float | array-like
|
231
|
-
Value(s) to set.
|
232
|
-
"""
|
233
|
-
|
234
|
-
self.M[key] = value # In-place assignment
|
235
|
-
|
236
|
-
#===================================
|
237
|
-
|
238
|
-
#-------------------------------
|
239
|
-
# Basic arithmetic operations
|
240
|
-
#-------------------------------
|
241
|
-
def __add__(self, other):
|
242
|
-
if isinstance(other, type(self)):
|
243
|
-
if not self.has_same_axis_as(other):
|
244
|
-
raise ValueError("Axes are not aligned for the operation.")
|
245
|
-
return np.add(self, other)
|
246
|
-
|
247
|
-
def __radd__(self, other):
|
248
|
-
if isinstance(other, type(self)):
|
249
|
-
if not self.has_same_axis_as(other):
|
250
|
-
raise ValueError("Axes are not aligned for the operation.")
|
251
|
-
return np.add(other, self)
|
252
|
-
|
253
|
-
def __sub__(self, other):
|
254
|
-
if isinstance(other, type(self)):
|
255
|
-
if not self.has_same_axis_as(other):
|
256
|
-
raise ValueError("Axes are not aligned for the operation.")
|
257
|
-
return np.subtract(self, other)
|
258
|
-
|
259
|
-
def __rsub__(self, other):
|
260
|
-
if isinstance(other, type(self)):
|
261
|
-
if not self.has_same_axis_as(other):
|
262
|
-
raise ValueError("Axes are not aligned for the operation.")
|
263
|
-
return np.subtract(other, self)
|
264
|
-
|
265
|
-
def __mul__(self, other):
|
266
|
-
if isinstance(other, type(self)):
|
267
|
-
if not self.has_same_axis_as(other):
|
268
|
-
raise ValueError("Axes are not aligned for the operation.")
|
269
|
-
return np.multiply(self, other)
|
270
|
-
|
271
|
-
def __rmul__(self, other):
|
272
|
-
if isinstance(other, type(self)):
|
273
|
-
if not self.has_same_axis_as(other):
|
274
|
-
raise ValueError("Axes are not aligned for the operation.")
|
275
|
-
return np.multiply(other, self)
|
276
|
-
|
277
|
-
def __truediv__(self, other):
|
278
|
-
if isinstance(other, type(self)):
|
279
|
-
if not self.has_same_axis_as(other):
|
280
|
-
raise ValueError("Axes are not aligned for the operation.")
|
281
|
-
return np.divide(self, other)
|
282
|
-
|
283
|
-
def __rtruediv__(self, other):
|
284
|
-
if isinstance(other, type(self)):
|
285
|
-
if not self.has_same_axis_as(other):
|
286
|
-
raise ValueError("Axes are not aligned for the operation.")
|
287
|
-
return np.divide(other, self)
|
288
|
-
|
289
|
-
def __floordiv__(self, other):
|
290
|
-
if isinstance(other, type(self)):
|
291
|
-
if not self.has_same_axis_as(other):
|
292
|
-
raise ValueError("Axes are not aligned for the operation.")
|
293
|
-
return np.floor_divide(self, other)
|
294
|
-
|
295
|
-
def __rfloordiv__(self, other):
|
296
|
-
if not self.has_same_axis_as(other):
|
297
|
-
raise ValueError("Axes are not aligned for the operation.")
|
298
|
-
return np.floor_divide(other, self)
|
299
|
-
|
300
|
-
def __pow__(self, other):
|
301
|
-
if isinstance(other, type(self)):
|
302
|
-
if not self.has_same_axis_as(other):
|
303
|
-
raise ValueError("Axes are not aligned for the operation.")
|
304
|
-
return np.power(self, other)
|
305
|
-
|
306
|
-
def __rpow__(self, other):
|
307
|
-
if isinstance(other, type(self)):
|
308
|
-
if not self.has_same_axis_as(other):
|
309
|
-
raise ValueError("Axes are not aligned for the operation.")
|
310
|
-
return np.power(other, self)
|
311
|
-
|
312
|
-
#===============================
|
313
|
-
|
314
|
-
|
315
|
-
#-------------------------------
|
316
|
-
# Basic comparison operations
|
317
|
-
#-------------------------------
|
318
|
-
def __eq__(self, other):
|
319
|
-
return np.equal(self, other)
|
320
|
-
|
321
|
-
def __ne__(self, other):
|
322
|
-
return np.not_equal(self, other)
|
323
|
-
|
324
|
-
def __lt__(self, other):
|
325
|
-
return np.less(self, other)
|
326
|
-
|
327
|
-
def __le__(self, other):
|
328
|
-
return np.less_equal(self, other)
|
329
|
-
|
330
|
-
def __gt__(self, other):
|
331
|
-
return np.greater(self, other)
|
332
|
-
|
333
|
-
def __ge__(self, other):
|
334
|
-
return np.greater_equal(self, other)
|
335
|
-
|
336
|
-
#===============================
|
337
|
-
|
338
|
-
|
339
|
-
#-----------------------------------
|
340
|
-
# Utility Methods
|
341
|
-
#-----------------------------------
|
342
|
-
|
343
|
-
def unpack(self):
|
344
|
-
"""
|
345
|
-
Unpacks the object into easy to work
|
346
|
-
with data structures.
|
347
|
-
|
348
|
-
Returns
|
349
|
-
-------
|
350
|
-
(np.ndarray, np.ndarray, np.ndarray)
|
351
|
-
- M: Signal data array.
|
352
|
-
- y: Signal Y-axis array.
|
353
|
-
- x: Signal X-axis array.
|
354
|
-
"""
|
355
|
-
|
356
|
-
M = self.M.values
|
357
|
-
y = self.y.values
|
358
|
-
x = self.x.values
|
359
|
-
|
360
|
-
return (M, y, x)
|
361
|
-
|
362
|
-
def copy(self) -> Self:
|
363
|
-
"""
|
364
|
-
Returns a new copy of the signal.
|
365
|
-
|
366
|
-
Returns
|
367
|
-
-------
|
368
|
-
Self
|
369
|
-
A new copy of the object.
|
370
|
-
"""
|
371
|
-
|
372
|
-
copied_M = self.M.copy()
|
373
|
-
copied_y = self.y.copy()
|
374
|
-
copied_x = self.x.copy()
|
375
|
-
title = self.title # Immutable, hence no need to copy
|
376
|
-
|
377
|
-
return self.__class__(M=copied_M, y=copied_y, x=copied_x, title=title)
|
378
|
-
|
379
|
-
def set_meta_info(self, title = None, M_label = None, y_label = None, x_label = None) -> None:
|
380
|
-
"""
|
381
|
-
Set meta info about the signals.
|
382
|
-
|
383
|
-
Parameters
|
384
|
-
----------
|
385
|
-
title: str
|
386
|
-
- Title for the signal
|
387
|
-
- e.g. "MyTitle"
|
388
|
-
M_label: str
|
389
|
-
- Label for the data that matrix is holding.
|
390
|
-
- e.g. "Intensity (dB)"
|
391
|
-
y_label: str
|
392
|
-
- Label for the y-axis.
|
393
|
-
- e.g. "Frequency (Hz)"
|
394
|
-
x_label: str
|
395
|
-
- Label for the x-axis.
|
396
|
-
- e.g. "Time (sec)"
|
397
|
-
Returns
|
398
|
-
-------
|
399
|
-
S2D
|
400
|
-
A new instance with updated meta info.
|
401
|
-
"""
|
402
|
-
|
403
|
-
M, y, x = self.M, self.y, self.x
|
404
|
-
|
405
|
-
M_label = str(M_label) if M_label is not None else M.label
|
406
|
-
y_label = str(y_label) if y_label is not None else y.label
|
407
|
-
x_label = str(x_label) if x_label is not None else x.label
|
408
|
-
title = str(title) if title is not None else self.title
|
409
|
-
|
410
|
-
# We create a new copy of the data and axis
|
411
|
-
new_M = M.set_meta_info(label=M_label)
|
412
|
-
new_y = y.set_meta_info(label=y_label)
|
413
|
-
new_x = x.set_meta_info(label=x_label)
|
414
|
-
|
415
|
-
return self.__class__(M=new_M, y=new_y, x=new_x, title=title)
|
416
|
-
|
417
|
-
|
418
|
-
def is_same_as(self, other: Self) -> bool:
|
419
|
-
"""
|
420
|
-
Check if two `S1D` instances are equal.
|
421
|
-
"""
|
422
|
-
|
423
|
-
if not isinstance(other, type(self)):
|
424
|
-
return False
|
425
|
-
|
426
|
-
if not self.M.is_same_as(other.M):
|
427
|
-
return False
|
428
|
-
|
429
|
-
if not self.y.is_same_as(other.y):
|
430
|
-
return False
|
431
|
-
|
432
|
-
if not self.x.is_same_as(other.x):
|
433
|
-
return False
|
434
|
-
|
435
|
-
return True
|
436
|
-
|
437
|
-
def has_same_axis_as(self, other) -> bool:
|
438
|
-
"""
|
439
|
-
Check if two 'S1D' instances have same
|
440
|
-
axis. Many operations need to satify this.
|
441
|
-
"""
|
442
|
-
return self.y.is_same_as(other.y) and self.x.is_same_as(other.x)
|
443
|
-
|
444
|
-
|
445
|
-
def mask(self, condition, set_to=None) -> Self:
|
446
|
-
"""
|
447
|
-
Mask the signal based on condition and
|
448
|
-
the values can be set.
|
449
|
-
|
450
|
-
Parameters
|
451
|
-
----------
|
452
|
-
condition: Callable
|
453
|
-
- Condition function to apply on values of the signal.
|
454
|
-
- E.g. lambda x: x > 10
|
455
|
-
set_to: Number
|
456
|
-
- Number to replace the masked position values.
|
457
|
-
|
458
|
-
Returns
|
459
|
-
-------
|
460
|
-
S2D
|
461
|
-
Masked Signal
|
462
|
-
"""
|
463
|
-
|
464
|
-
mask = condition(self)
|
465
|
-
new_val = set_to
|
466
|
-
|
467
|
-
if set_to is None: # Return the mask as the same signal but with booleans
|
468
|
-
return mask
|
469
|
-
|
470
|
-
else:
|
471
|
-
# We apply the mask and update the signal data
|
472
|
-
new_data = self.M.mask(condition=condition, set_to=new_val)
|
473
|
-
|
474
|
-
# Since we're just updating the data, there is no change in the axis
|
475
|
-
return self.__class__(M=new_data, y=self.y.copy(), x=self.x.copy(), title=self.title)
|
476
|
-
|
477
|
-
#===================================
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
#-----------------------------------
|
485
|
-
# Visualisation
|
486
|
-
#-----------------------------------
|
487
|
-
|
488
|
-
def plot(
|
489
|
-
self,
|
490
|
-
ax = None,
|
491
|
-
cmap = "gray_r",
|
492
|
-
title = None,
|
493
|
-
M_label = None,
|
494
|
-
x_label = None,
|
495
|
-
y_label = None,
|
496
|
-
y_lim = None,
|
497
|
-
x_lim = None,
|
498
|
-
highlight_regions = None,
|
499
|
-
vlines = None,
|
500
|
-
hlines = None,
|
501
|
-
origin = "lower", # or "lower"
|
502
|
-
gamma = None,
|
503
|
-
show_colorbar = True,
|
504
|
-
cax = None,
|
505
|
-
show_grid = True,
|
506
|
-
tick_mode = "center", # "center" or "edge"
|
507
|
-
n_ticks = None,
|
508
|
-
) -> "plt.Figure":
|
509
|
-
"""
|
510
|
-
Plot the S2D instance using Matplotlib.
|
511
|
-
|
512
|
-
|
513
|
-
Parameters
|
514
|
-
----------
|
515
|
-
ax : matplotlib.axes.Axes | None
|
516
|
-
- If you want to plot the signal on a given matplotlib axes, you can pass the ax here. We do not return any figure in this case.
|
517
|
-
- If not passed, we create a new figure, plots the signal on it and then return the figure.
|
518
|
-
cmap : str, default "gray_r"
|
519
|
-
Colormap used for the image.
|
520
|
-
title : str | None
|
521
|
-
- Title for the plot.
|
522
|
-
- If not passed, we use the default set during signal instantiation.
|
523
|
-
y_lim : tuple[float, float] | None
|
524
|
-
- Limits for the y-axis.
|
525
|
-
x_lim : tuple[float, float] | None
|
526
|
-
- Limits for the x-axis.
|
527
|
-
vlines: list[float]
|
528
|
-
- List of x values to draw vertical lines.
|
529
|
-
- e.g. [10, 13.5]
|
530
|
-
hlines: list[float]
|
531
|
-
- List of data values to draw horizontal lines.
|
532
|
-
- e.g. [10, 13.5]
|
533
|
-
highlight_regions : list[tuple[float, float, str]] | None
|
534
|
-
- List of time intervals to highlight on the plot.
|
535
|
-
- [(start, end, 'tag')]
|
536
|
-
origin : {"lower", "upper"}, default "lower"
|
537
|
-
Origin position for the image (for flipping vertical axis).
|
538
|
-
gamma : float or int, optional
|
539
|
-
If specified, apply log-compression using log(1 + S * factor).
|
540
|
-
show_colorbar : bool
|
541
|
-
- Whether to display the colorbar.
|
542
|
-
- Defaults to True.
|
543
|
-
cax : matplotlib.axes.Axes | None
|
544
|
-
- Axis to draw the colorbar on. If None, uses default placement.
|
545
|
-
- Defaults to None
|
546
|
-
show_grid : bool
|
547
|
-
- Whether to show the major gridlines.
|
548
|
-
- Defaults to None.
|
549
|
-
tick_mode : {"center", "edge"}
|
550
|
-
- Whether to place ticks at bin centers or edges.
|
551
|
-
- Default to "center"
|
552
|
-
n_ticks : tuple[int]
|
553
|
-
- Number of ticks (y_ticks, x_ticks) to display on each axis.
|
554
|
-
- Defaults to None
|
555
|
-
|
556
|
-
Returns
|
557
|
-
-------
|
558
|
-
matplotlib.figure.Figure | None
|
559
|
-
- The figure object containing the plot.
|
560
|
-
- None if ax is provided.
|
561
|
-
"""
|
562
|
-
from modusa.tools.plotter import Plotter
|
563
|
-
|
564
|
-
M, y, x = self._M, self._y, self._x
|
565
|
-
M_val, y_val, x_val = M._values, y._values, x._values
|
566
|
-
|
567
|
-
M_label = M_label or M._label
|
568
|
-
y_label = y_label or y._label
|
569
|
-
x_label = x_label or x._label
|
570
|
-
|
571
|
-
title = title or self._title
|
572
|
-
|
573
|
-
fig = Plotter.plot_matrix(M=M_val, r=y_val, c=x_val, ax=ax, cmap=cmap, title=title, M_label=M_label, r_label=y_label, c_label=x_label, r_lim=y_lim, c_lim=x_lim,
|
574
|
-
highlight_regions=highlight_regions, vlines=vlines, hlines=hlines, origin=origin, gamma=gamma, show_colorbar=show_colorbar, cax=cax, show_grid=show_grid,
|
575
|
-
tick_mode=tick_mode, n_ticks=n_ticks)
|
576
|
-
|
577
|
-
return fig
|
578
|
-
|
579
|
-
#===================================
|
580
|
-
|
581
|
-
#-----------------------------------
|
582
|
-
# Information
|
583
|
-
#-----------------------------------
|
584
|
-
|
585
|
-
def print_info(self) -> None:
|
586
|
-
"""Print key information about the signal."""
|
587
|
-
|
588
|
-
print("-"*50)
|
589
|
-
print(f"{'Title':<20}: {self._title}")
|
590
|
-
print("-"*50)
|
591
|
-
print(f"{'Type':<20}: {self.__class__.__name__}")
|
592
|
-
print(f"{'Shape':<20}: {self.shape} (freq bins × time frames)")
|
593
|
-
|
594
|
-
# Inheritance chain
|
595
|
-
cls_chain = " → ".join(cls.__name__ for cls in reversed(self.__class__.__mro__[:-1]))
|
596
|
-
print(f"{'Inheritance':<20}: {cls_chain}")
|
597
|
-
print("=" * 50)
|
598
|
-
|
599
|
-
def __str__(self):
|
600
|
-
arr_str = np.array2string(
|
601
|
-
np.asarray(self),
|
602
|
-
separator=", ",
|
603
|
-
threshold=20, # limit number of elements shown
|
604
|
-
edgeitems=2, # show first/last 2 rows and columns
|
605
|
-
max_line_width=120, # avoid wrapping
|
606
|
-
)
|
607
|
-
return f"{self._nickname}({arr_str})"
|
608
|
-
|
609
|
-
def __repr__(self):
|
610
|
-
arr_str = np.array2string(
|
611
|
-
np.asarray(self),
|
612
|
-
separator=", ",
|
613
|
-
threshold=20, # limit number of elements shown
|
614
|
-
edgeitems=2, # show first/last 2 rows and columns
|
615
|
-
max_line_width=120, # avoid wrapping
|
616
|
-
)
|
617
|
-
return f"{self._nickname}({arr_str})"
|
618
|
-
|
619
|
-
#===================================
|