Skip to content

Commit 4298960

Browse files
authored
waveform: Add frequency spectrum support (#26)
* waveform: Add Spectrum class * tests: Add Spectrum tests * tests: Add ComplexWaveform magic method tests and fix a repr bug * waveform: Refactor error messages * waveform: Fix Pyright warnings
1 parent e3f8e45 commit 4298960

File tree

9 files changed

+2537
-78
lines changed

9 files changed

+2537
-78
lines changed

src/nitypes/waveform/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
To construct a complex waveform, use the :any:`ComplexWaveform` class:
6666
6767
>>> ComplexWaveform.from_array_1d([1 + 2j, 3 + 4j], np.complex128)
68-
nitypes.waveform.ComplexWaveform(2, complex128, raw_data=array([1.+2.j, 3.+4.j]))
68+
nitypes.waveform.ComplexWaveform(2, raw_data=array([1.+2.j, 3.+4.j]))
6969
7070
Scaling complex-number data
7171
---------------------------
@@ -84,7 +84,22 @@
8484
array([(1, 2), (3, 4)], dtype=[('real', '<i2'), ('imag', '<i2')])
8585
>>> wfm.scaled_data
8686
array([2.5+4.j, 6.5+8.j])
87-
"""
87+
88+
Frequency Spectrums
89+
===================
90+
91+
A frequency spectrum represents an analog signal with frequency information and extended properties
92+
such as units.
93+
94+
Constructing spectrums
95+
----------------------
96+
97+
To construct a spectrum, use the :any:`Spectrum` class:
98+
99+
>>> Spectrum.from_array_1d([1, 2, 3], np.float64, start_frequency=100, frequency_increment=10) # doctest: +NORMALIZE_WHITESPACE
100+
nitypes.waveform.Spectrum(3, data=array([1., 2., 3.]), start_frequency=100.0,
101+
frequency_increment=10.0)
102+
""" # noqa: W505 - doc line too long
88103

89104
from nitypes.waveform._analog import AnalogWaveform
90105
from nitypes.waveform._complex import ComplexWaveform
@@ -100,6 +115,7 @@
100115
NoneScaleMode,
101116
ScaleMode,
102117
)
118+
from nitypes.waveform._spectrum import Spectrum
103119
from nitypes.waveform._timing import (
104120
BaseTiming,
105121
PrecisionTiming,
@@ -122,6 +138,7 @@
122138
"SampleIntervalMode",
123139
"ScaleMode",
124140
"ScalingMismatchWarning",
141+
"Spectrum",
125142
"Timing",
126143
"TimingMismatchError",
127144
"TimingMismatchWarning",
@@ -141,6 +158,7 @@
141158
SampleIntervalMode.__module__ = __name__
142159
ScaleMode.__module__ = __name__
143160
ScalingMismatchWarning.__module__ = __name__
161+
Spectrum.__module__ = __name__
144162
Timing.__module__ = __name__
145163
TimingMismatchError.__module__ = __name__
146164
TimingMismatchWarning.__module__ = __name__

src/nitypes/waveform/_analog.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
265265
start_index: SupportsIndex | None = ...,
266266
capacity: SupportsIndex | None = ...,
267267
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
268+
copy_extended_properties: bool = ...,
268269
timing: Timing | PrecisionTiming | None = ...,
269270
scale_mode: ScaleMode | None = ...,
270271
) -> None: ...
@@ -279,6 +280,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
279280
start_index: SupportsIndex | None = ...,
280281
capacity: SupportsIndex | None = ...,
281282
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
283+
copy_extended_properties: bool = ...,
282284
timing: Timing | PrecisionTiming | None = ...,
283285
scale_mode: ScaleMode | None = ...,
284286
) -> None: ...
@@ -293,6 +295,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
293295
start_index: SupportsIndex | None = ...,
294296
capacity: SupportsIndex | None = ...,
295297
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
298+
copy_extended_properties: bool = ...,
296299
timing: Timing | PrecisionTiming | None = ...,
297300
scale_mode: ScaleMode | None = ...,
298301
) -> None: ...
@@ -307,6 +310,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
307310
start_index: SupportsIndex | None = ...,
308311
capacity: SupportsIndex | None = ...,
309312
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
313+
copy_extended_properties: bool = ...,
310314
timing: Timing | PrecisionTiming | None = ...,
311315
scale_mode: ScaleMode | None = ...,
312316
) -> None: ...

src/nitypes/waveform/_complex.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
253253
start_index: SupportsIndex | None = ...,
254254
capacity: SupportsIndex | None = ...,
255255
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
256+
copy_extended_properties: bool = ...,
256257
timing: Timing | PrecisionTiming | None = ...,
257258
scale_mode: ScaleMode | None = ...,
258259
) -> None: ...
@@ -267,6 +268,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
267268
start_index: SupportsIndex | None = ...,
268269
capacity: SupportsIndex | None = ...,
269270
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
271+
copy_extended_properties: bool = ...,
270272
timing: Timing | PrecisionTiming | None = ...,
271273
scale_mode: ScaleMode | None = ...,
272274
) -> None: ...
@@ -281,6 +283,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
281283
start_index: SupportsIndex | None = ...,
282284
capacity: SupportsIndex | None = ...,
283285
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
286+
copy_extended_properties: bool = ...,
284287
timing: Timing | PrecisionTiming | None = ...,
285288
scale_mode: ScaleMode | None = ...,
286289
) -> None: ...
@@ -295,6 +298,7 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
295298
start_index: SupportsIndex | None = ...,
296299
capacity: SupportsIndex | None = ...,
297300
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
301+
copy_extended_properties: bool = ...,
298302
timing: Timing | PrecisionTiming | None = ...,
299303
scale_mode: ScaleMode | None = ...,
300304
) -> None: ...

src/nitypes/waveform/_exceptions.py

Lines changed: 113 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,129 @@
11
from __future__ import annotations
22

3+
from typing_extensions import Literal
4+
35

46
class TimingMismatchError(RuntimeError):
57
"""Exception used when appending waveforms with mismatched timing."""
68

79
pass
810

911

10-
def input_array_data_type_mismatch(input_dtype: object, waveform_dtype: object) -> TypeError:
11-
"""Create a TypeError for an input array data type mismatch."""
12-
return TypeError(
13-
"The data type of the input array must match the waveform data type.\n\n"
14-
f"Input array data type: {input_dtype}\n"
15-
f"Waveform data type: {waveform_dtype}"
12+
def capacity_mismatch(capacity: int, array_length: int) -> ValueError:
13+
"""Create a ValueError for an invalid capacity."""
14+
return ValueError(
15+
f"The capacity must match the input array length.\n\n"
16+
f"Capacity: {capacity}\n"
17+
f"Array length: {array_length}"
1618
)
1719

1820

19-
def input_waveform_data_type_mismatch(input_dtype: object, waveform_dtype: object) -> TypeError:
20-
"""Create a TypeError for an input waveform data type mismatch."""
21+
def capacity_too_small(capacity: int, min_capacity: int, object_description: str) -> ValueError:
22+
"""Create a ValueError for an invalid capacity argument."""
23+
return ValueError(
24+
f"The capacity must be equal to or greater than the number of samples in the {object_description}.\n\n"
25+
f"Capacity: {capacity}\n"
26+
f"Number of samples: {min_capacity}"
27+
)
28+
29+
30+
def data_type_mismatch(
31+
arg_description: Literal["input array", "input spectrum", "input waveform"],
32+
arg_dtype: object,
33+
other_description: Literal["requested", "spectrum", "waveform"],
34+
other_dtype: object,
35+
) -> TypeError:
36+
"""Create a TypeError for a data type mismatch."""
37+
arg_key = {
38+
"input array": "Input array data type",
39+
"input spectrum": "Input spectrum data type",
40+
"input waveform": "Input waveform data type",
41+
}
42+
other_key = {
43+
"requested": "Requested data type",
44+
"spectrum": "Spectrum data type",
45+
"waveform": "Waveform data type",
46+
}
2147
return TypeError(
22-
"The data type of the input waveform must match the waveform data type.\n\n"
23-
f"Input waveform data type: {input_dtype}\n"
24-
f"Waveform data type: {waveform_dtype}"
48+
f"The data type of the {arg_description} must match the {other_description} data type.\n\n"
49+
f"{arg_key[arg_description]}: {arg_dtype}\n"
50+
f"{other_key[other_description]}: {other_dtype}"
51+
)
52+
53+
54+
def irregular_timestamp_count_mismatch(
55+
irregular_timestamp_count: int,
56+
other_description: Literal["input array length", "number of samples in the waveform"],
57+
other: int,
58+
*,
59+
reversed: bool = False,
60+
) -> ValueError:
61+
"""Create a ValueError for an irregular timestamp count mismatch."""
62+
other_key = {
63+
"input array length": "Array length",
64+
"number of samples in the waveform": "Number of samples",
65+
}
66+
if reversed:
67+
raise ValueError(
68+
"The input array length must be equal to the number of irregular timestamps.\n\n"
69+
f"{other_key[other_description]}: {other}\n"
70+
f"Number of timestamps: {irregular_timestamp_count}"
71+
)
72+
else:
73+
raise ValueError(
74+
f"The number of irregular timestamps must be equal to the {other_description}.\n\n"
75+
f"Number of timestamps: {irregular_timestamp_count}\n"
76+
f"{other_key[other_description]}: {other}"
77+
)
78+
79+
80+
def start_index_too_large(
81+
start_index: int,
82+
capacity_description: Literal[
83+
"capacity",
84+
"input array length",
85+
"number of samples in the spectrum",
86+
"number of samples in the waveform",
87+
],
88+
capacity: int,
89+
) -> ValueError:
90+
"""Create a ValueError for an invalid start index argument."""
91+
capacity_key = {
92+
"capacity": "Capacity",
93+
"input array length": "Array length",
94+
"number of samples in the spectrum": "Number of samples",
95+
"number of samples in the waveform": "Number of samples",
96+
}
97+
return ValueError(
98+
f"The start index must be less than or equal to the {capacity_description}.\n\n"
99+
f"Start index: {start_index}\n"
100+
f"{capacity_key[capacity_description]}: {capacity}"
101+
)
102+
103+
104+
def start_index_or_sample_count_too_large(
105+
start_index: int,
106+
sample_count: int,
107+
capacity_description: Literal[
108+
"capacity",
109+
"input array length",
110+
"number of samples in the spectrum",
111+
"number of samples in the waveform",
112+
],
113+
capacity: int,
114+
) -> ValueError:
115+
"""Create a ValueError for an invalid start index or sample count argument."""
116+
capacity_key = {
117+
"capacity": "Capacity",
118+
"input array length": "Array length",
119+
"number of samples in the spectrum": "Number of samples",
120+
"number of samples in the waveform": "Number of samples",
121+
}
122+
return ValueError(
123+
f"The sum of the start index and sample count must be less than or equal to the {capacity_description}.\n\n"
124+
f"Start index: {start_index}\n"
125+
f"Sample count: {sample_count}\n"
126+
f"{capacity_key[capacity_description]}: {capacity}"
25127
)
26128

27129

0 commit comments

Comments
 (0)