Skip to content

Commit 47fdca0

Browse files
committed
Moved windowing function outside of plot_spectrum
1 parent 1a79a6c commit 47fdca0

File tree

2 files changed

+45
-21
lines changed

2 files changed

+45
-21
lines changed

adc_eval/eval/spectrum.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ def get_spectrum(data, fs=1, nfft=2**12, single_sided=True):
3636
return (freq_ds, psd_ds * fs / nfft)
3737

3838

39+
def window_data(data, window="rectangular"):
40+
"""Applies a window to the time-domain data."""
41+
wsize = data.size
42+
windows = {
43+
"rectangular": (np.ones(wsize), 1.0),
44+
"hanning": (np.hanning(wsize), 1.633)
45+
}
46+
47+
if window not in windows:
48+
print(f"WARNING: {window} not implemented. Defaulting to 'rectangular'.")
49+
window = "rectangular"
50+
51+
wscale = windows[window][1]
52+
53+
return data * windows[window][0] * wscale
54+
55+
3956
def plot_spectrum(
4057
data,
4158
fs=1,
@@ -50,11 +67,6 @@ def plot_spectrum(
5067
fscale="MHz",
5168
):
5269
"""Plot Power Spectrum for input signal."""
53-
wsize = data.size
54-
windows = {
55-
"rectangular": np.ones(wsize),
56-
"hanning": np.hanning(wsize),
57-
}
5870

5971
fscalar = {
6072
"uHz": 1e-6,
@@ -65,25 +77,18 @@ def plot_spectrum(
6577
"GHz": 1e9,
6678
"THz": 1e12,
6779
}
68-
69-
if window not in windows:
70-
print(f"WARNING: {window} not implemented. Defaulting to 'rectangular'.")
71-
window = "rectangular"
72-
7380
if fscale not in fscalar:
7481
print(f"WARNING: {fscale} not implemented. Defaulting to 'MHz'.")
7582
fscale = "MHz"
7683

77-
wscale = {
78-
"rectangular": 1.0,
79-
"hanning": 1.633,
80-
}[window]
81-
82-
(freq, pwr) = get_spectrum(
83-
data * windows[window] * wscale, fs=fs, nfft=nfft, single_sided=single_sided
84-
)
84+
# Window the data and get the single or dual-sided spectrum
85+
wdata = window_data(data, window=window)
86+
(freq, pwr) = get_spectrum(wdata, fs=fs, nfft=nfft, single_sided=single_sided)
87+
88+
# Calculate the fullscale range of the spectrum in Watts
8589
full_scale = calc.dBW(dr**2 / 8)
8690

91+
# Determine what y-axis scaling to use
8792
yaxis_lut = {
8893
"power": [0, "dB"],
8994
"fullscale": [full_scale, "dBFS"],
@@ -102,10 +107,14 @@ def plot_spectrum(
102107
)
103108
print(" Defaulting to Hz.")
104109

110+
# Convert to dBW and perform scalar based on y-axis scaling input
105111
psd_out = calc.dBW(pwr, places=3) - scalar
112+
113+
# Use Watts if magnitude y-axis scaling is desired
106114
if lut_key in ["magnitude"]:
107115
psd_out = pwr
108116

117+
# Get single-sided spectrum for consistent SNDR and harmonic calculation behavior
109118
f_ss = freq
110119
psd_ss = pwr
111120
if not single_sided:
@@ -115,7 +124,6 @@ def plot_spectrum(
115124
)
116125

117126
sndr_stats = calc.sndr_sfdr(psd_ss, f_ss, fs, nfft, leak=leak, full_scale=full_scale)
118-
119127
harm_stats = calc.find_harmonics(
120128
psd_ss,
121129
f_ss,
@@ -127,10 +135,13 @@ def plot_spectrum(
127135
fscale=xscale,
128136
)
129137

138+
# Merge the two stat dictionaries into one for convenient access
130139
stats = {**sndr_stats, **harm_stats}
131140

141+
# Change the x-axis minimum value based on single or dual-sided selection
132142
xmin = 0 if single_sided else -fs / 2e6
133143

144+
# If plotting, prep plot and generate all required axis strings
134145
if not no_plot:
135146
plt_str = calc.get_plot_string(stats, full_scale, fs, nfft, window, xscale, fscale)
136147
fig, ax = plt.subplots(figsize=(15, 8))

tests/eval/test_spectrum.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,22 @@ def test_get_spectrum(mock_calc_psd):
2727
data = np.array([1])
2828
exp_spectrum = np.array([fs / nfft])
2929

30-
mock_calc_psd.return_value = (None, data, None, data)
30+
mock_calc_psd.return_value = (None, data, None, 2*data)
3131

32-
assert (None, exp_spectrum) == spectrum.get_spectrum(None, fs=fs, nfft=nfft)
32+
assert (None, exp_spectrum) == spectrum.get_spectrum(None, fs=fs, nfft=nfft, single_sided=True)
33+
34+
35+
@mock.patch("adc_eval.eval.spectrum.calc_psd")
36+
def test_get_spectrum_dual(mock_calc_psd):
37+
"""Test that the get_spectrum method returns dual-sided power spectrum."""
38+
fs = 4
39+
nfft = 3
40+
data = np.array([1])
41+
exp_spectrum = np.array([fs / nfft])
42+
43+
mock_calc_psd.return_value = (None, data, None, 2*data)
44+
45+
assert (None, 2*exp_spectrum) == spectrum.get_spectrum(None, fs=fs, nfft=nfft, single_sided=False)
3346

3447

3548
@pytest.mark.parametrize("data", [np.random.randn(NLEN) for _ in range(10)])

0 commit comments

Comments
 (0)