Skip to content

Commit 692c0d9

Browse files
authored
Merge pull request #76 from fronzbot/fix-leaky-signals
Fix some math to account for leaky bins
2 parents c783213 + cd89138 commit 692c0d9

File tree

1 file changed

+30
-12
lines changed

1 file changed

+30
-12
lines changed

adc_eval/spectrum.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def enob(sndr, places=1):
2626
def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
2727
"""Get SNDR and SFDR."""
2828
# Zero the DC bin
29-
spectrum[0] = 0
29+
for i in range(0, leak + 1):
30+
spectrum[i] = 0
3031
bin_sig = np.argmax(spectrum)
3132
psig = sum(spectrum[i] for i in range(bin_sig - leak, bin_sig + leak + 1))
3233
spectrum_n = spectrum
@@ -79,7 +80,7 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
7980
return stats
8081

8182

82-
def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20):
83+
def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20, fscale=1e6):
8384
"""Get the harmonic contents of the data."""
8485
harm_stats = {"harm": {}}
8586
harm_index = 2
@@ -109,7 +110,7 @@ def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20):
109110

110111
harm_stats["harm"][harm_index]["bin"] = bin_harm_max
111112
harm_stats["harm"][harm_index]["power"] = pwr_max
112-
harm_stats["harm"][harm_index]["freq"] = round(freq[bin_harm] / 1e6, 1)
113+
harm_stats["harm"][harm_index]["freq"] = round(freq[bin_harm] / fscale, 1)
113114
harm_stats["harm"][harm_index]["dBc"] = dBW(pwr_max / psig)
114115
harm_stats["harm"][harm_index]["dB"] = dBW(pwr_max)
115116

@@ -151,6 +152,7 @@ def plot_spectrum(
151152
window="rectangular",
152153
no_plot=False,
153154
yaxis="power",
155+
fscale="MHz",
154156
):
155157
"""Plot Power Spectrum for input signal."""
156158
wsize = data.size
@@ -159,10 +161,21 @@ def plot_spectrum(
159161
"hanning": np.hanning(wsize),
160162
}
161163

164+
fscalar = {
165+
"Hz": 1,
166+
"kHz": 1e3,
167+
"MHz": 1e6,
168+
"GHz": 1e9,
169+
}
170+
162171
if window not in windows:
163172
print(f"WARNING: {window} not implemented. Defaulting to 'rectangular'.")
164173
window = "rectangular"
165174

175+
if fscale not in fscalar:
176+
print(f"WARNING: {fscale} not implemented. Defaulting to 'MHz'.")
177+
fscale = "MHz"
178+
166179
wscale = {
167180
"rectangular": 1.0,
168181
"hanning": 1.633,
@@ -179,6 +192,8 @@ def plot_spectrum(
179192

180193
pwr_dB = 10 * np.log10(pwr) - scalar
181194

195+
xscale = fscalar[fscale]
196+
182197
sndr_stats = sndr_sfdr(pwr, freq, fs, nfft, leak=leak, full_scale=full_scale)
183198
harm_stats = find_harmonics(
184199
pwr,
@@ -188,19 +203,20 @@ def plot_spectrum(
188203
sndr_stats["sig"]["power"],
189204
harms=harmonics,
190205
leak=leak,
206+
fscale=xscale,
191207
)
192208

193209
stats = {**sndr_stats, **harm_stats}
194210

195211
if not no_plot:
196-
plt_str = get_plot_string(stats, full_scale, fs, nfft, window)
212+
plt_str = get_plot_string(stats, full_scale, fs, nfft, window, xscale, fscale)
197213

198214
fig, ax = plt.subplots(figsize=(15, 8))
199-
ax.plot(freq / 1e6, pwr_dB)
215+
ax.plot(freq / xscale, pwr_dB)
200216
ax.set_ylabel(f"Power Spectrum ({yunits})", fontsize=18)
201-
ax.set_xlabel("Frequency (MHz)", fontsize=16)
217+
ax.set_xlabel(f"Frequency ({fscale})", fontsize=16)
202218
ax.set_title("Output Power Spectrum", fontsize=16)
203-
ax.set_xlim([0, fs / 2e6])
219+
ax.set_xlim([0, fs / (2 * xscale)])
204220
ax.set_ylim([1.1 * min(pwr_dB), 0])
205221
ax.annotate(
206222
plt_str,
@@ -241,7 +257,7 @@ def plot_spectrum(
241257
return (pwr, stats)
242258

243259

244-
def get_plot_string(stats, full_scale, fs, nfft, window):
260+
def get_plot_string(stats, full_scale, fs, nfft, window, xscale=1e6, fscale="MHz"):
245261
"""Generate plot string from stats dict."""
246262

247263
plt_str = "==== FFT ====\n"
@@ -252,22 +268,22 @@ def get_plot_string(stats, full_scale, fs, nfft, window):
252268
plt_str += "==== Signal ====\n"
253269
plt_str += f"FullScale = {full_scale} dB\n"
254270
plt_str += f"Psig = {stats['sig']['dBFS']} dBFS ({stats['sig']['dB']} dB)\n"
255-
plt_str += f"fsig = {round(stats['sig']['freq']/1e6, 2)} MHz\n"
256-
plt_str += f"fsamp = {round(fs/1e6, 2)} MHz\n"
271+
plt_str += f"fsig = {round(stats['sig']['freq']/xscale, 2)} {fscale}\n"
272+
plt_str += f"fsamp = {round(fs/xscale, 2)} {fscale}\n"
257273
plt_str += "\n"
258274
plt_str += "==== SNDR/SFDR ====\n"
259275
plt_str += f"ENOB = {stats['enob']['bits']} bits\n"
260276
plt_str += f"SNDR = {stats['sndr']['dBFS']} dBFS ({stats['sndr']['dBc']} dBc)\n"
261277
plt_str += f"SFDR = {stats['sfdr']['dBFS']} dBFS ({stats['sfdr']['dBc']} dBc)\n"
262278
plt_str += f"Pspur = {stats['spur']['dBFS']} dBFS\n"
263-
plt_str += f"fspur = {round(stats['spur']['freq']/1e6, 2)} MHz\n"
279+
plt_str += f"fspur = {round(stats['spur']['freq']/xscale, 2)} {fscale}\n"
264280
plt_str += f"Noise Floor = {stats['noise']['dBHz']} dBFS\n"
265281
plt_str += f"NSD = {stats['noise']['NSD']} dBFS\n"
266282
plt_str += "\n"
267283
plt_str += "==== Harmonics ====\n"
268284

269285
for hindex, hdata in stats["harm"].items():
270-
plt_str += f"HD{hindex} = {round(hdata['dB'] - full_scale, 1)} dBFS @ {hdata['freq']} MHz\n"
286+
plt_str += f"HD{hindex} = {round(hdata['dB'] - full_scale, 1)} dBFS @ {hdata['freq']} {fscale}\n"
271287

272288
plt_str += "\n"
273289

@@ -284,6 +300,7 @@ def analyze(
284300
window="rectangular",
285301
no_plot=False,
286302
yaxis="fullscale",
303+
fscale="MHz",
287304
):
288305
"""Perform spectral analysis on input waveform."""
289306
(spectrum, stats) = plot_spectrum(
@@ -296,6 +313,7 @@ def analyze(
296313
window=window,
297314
no_plot=no_plot,
298315
yaxis=yaxis,
316+
fscale=fscale,
299317
)
300318

301319
return (spectrum, stats)

0 commit comments

Comments
 (0)