22
33import numpy as np
44import matplotlib .pyplot as plt
5-
6-
7- def db_to_pow (value , places = 3 ):
8- """Convert dBW to W."""
9- if isinstance (value , np .ndarray ):
10- return round (10 ** (0.1 * value ), places )
11- return round (10 ** (0.1 * value ), places )
12-
13-
14- def dBW (value , places = 1 ):
15- """Convert to dBW."""
16- if isinstance (value , np .ndarray ):
17- return round (10 * np .log10 (value ), places )
18- return round (10 * np .log10 (value ), places )
19-
20-
21- def enob (sndr , places = 1 ):
22- """Return ENOB for given SNDR."""
23- return round ((sndr - 1.76 ) / 6.02 , places )
24-
25-
26- def sndr_sfdr (spectrum , freq , fs , nfft , leak , full_scale = 0 ):
27- """Get SNDR and SFDR."""
28-
29- # Zero the DC bin
30- for i in range (0 , leak + 1 ):
31- spectrum [i ] = 0
32- bin_sig = np .argmax (spectrum )
33- psig = sum (spectrum [i ] for i in range (bin_sig - leak , bin_sig + leak + 1 ))
34- spectrum_n = spectrum .copy ()
35- spectrum_n [bin_sig ] = 0
36- fbin = fs / nfft
37-
38- for i in range (bin_sig - leak , bin_sig + leak + 1 ):
39- spectrum_n [i ] = 0
40-
41- bin_spur = np .argmax (spectrum_n )
42- pspur = spectrum [bin_spur ]
43-
44- noise_power = sum (spectrum_n )
45- noise_floor = 2 * noise_power / nfft
46-
47- stats = {}
48-
49- stats ["sig" ] = {
50- "freq" : freq [bin_sig ],
51- "bin" : bin_sig ,
52- "power" : psig ,
53- "dB" : dBW (psig ),
54- "dBFS" : round (dBW (psig ) - full_scale , 1 ),
55- }
56-
57- stats ["spur" ] = {
58- "freq" : freq [bin_spur ],
59- "bin" : bin_spur ,
60- "power" : pspur ,
61- "dB" : dBW (pspur ),
62- "dBFS" : round (dBW (pspur ) - full_scale , 1 ),
63- }
64- stats ["noise" ] = {
65- "floor" : noise_floor ,
66- "power" : noise_power ,
67- "rms" : np .sqrt (noise_power ),
68- "dBHz" : round (dBW (noise_floor , 3 ) - full_scale , 1 ),
69- "NSD" : round (dBW (noise_floor , 3 ) - full_scale - 2 * dBW (fbin , 3 ), 1 ),
70- }
71- stats ["sndr" ] = {
72- "dBc" : dBW (psig / noise_power ),
73- "dBFS" : round (full_scale - dBW (noise_power ), 1 ),
74- }
75- stats ["sfdr" ] = {
76- "dBc" : dBW (psig / pspur ),
77- "dBFS" : round (full_scale - dBW (pspur ), 1 ),
78- }
79- stats ["enob" ] = {"bits" : enob (stats ["sndr" ]["dBFS" ])}
80-
81- return stats
82-
83-
84- def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 , fscale = 1e6 ):
85- """Get the harmonic contents of the data."""
86- harm_stats = {"harm" : {}}
87- harm_index = 2
88- for harm in bin_sig * np .arange (2 , harms + 1 ):
89- harm_stats ["harm" ][harm_index ] = {}
90- zone = np .floor (harm / (nfft / 2 )) + 1
91- if zone % 2 == 0 :
92- bin_harm = int (nfft / 2 - (harm - (zone - 1 ) * nfft / 2 ))
93- else :
94- bin_harm = int (harm - (zone - 1 ) * nfft / 2 )
95-
96- # Make sure we pick the max bin where power is maximized; due to spectral leakage
97- # if bin_harm == nfft/2, set to bin of 0
98- if bin_harm == nfft / 2 :
99- bin_harm = 0
100- pwr_max = spectrum [bin_harm ]
101- bin_harm_max = bin_harm
102- for i in range (bin_harm - leak , bin_harm + leak + 1 ):
103- try :
104- pwr = spectrum [i ]
105- if pwr > pwr_max :
106- bin_harm_max = i
107- pwr_max = pwr
108- except IndexError :
109- # bin + leakage out of bounds, so stop looking
110- break
111-
112- harm_stats ["harm" ][harm_index ]["bin" ] = bin_harm_max
113- harm_stats ["harm" ][harm_index ]["power" ] = pwr_max
114- harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / fscale , 1 )
115- harm_stats ["harm" ][harm_index ]["dBc" ] = dBW (pwr_max / psig )
116- harm_stats ["harm" ][harm_index ]["dB" ] = dBW (pwr_max )
117-
118- harm_index = harm_index + 1
119-
120- return harm_stats
5+ from adc_eval .eval import calc
1216
1227
1238def calc_psd (data , fs , nfft = 2 ** 12 ):
@@ -197,12 +82,12 @@ def plot_spectrum(
19782 (freq , pwr ) = get_spectrum (
19883 data * windows [window ] * wscale , fs = fs , nfft = nfft , single_sided = single_sided
19984 )
200- full_scale = dBW (dr ** 2 / 8 )
85+ full_scale = calc . dBW (dr ** 2 / 8 )
20186
20287 yaxis_lut = {
20388 "power" : [0 , "dB" ],
204- "fullscale" : [dBW ( dr ** 2 / 8 ) , "dBFS" ],
205- "normalize" : [max (dBW (pwr )), "dB Normalized" ],
89+ "fullscale" : [full_scale , "dBFS" ],
90+ "normalize" : [max (calc . dBW (pwr )), "dB Normalized" ],
20691 "magnitude" : [0 , "W" ],
20792 }
20893
@@ -217,7 +102,7 @@ def plot_spectrum(
217102 )
218103 print (" Defaulting to Hz." )
219104
220- psd_out = 10 * np . log10 (pwr ) - scalar
105+ psd_out = calc . dBW (pwr , places = 3 ) - scalar
221106 if lut_key in ["magnitude" ]:
222107 psd_out = pwr
223108
@@ -229,9 +114,9 @@ def plot_spectrum(
229114 data * windows [window ] * wscale , fs = fs , nfft = nfft , single_sided = True
230115 )
231116
232- sndr_stats = sndr_sfdr (psd_ss , f_ss , fs , nfft , leak = leak , full_scale = full_scale )
117+ sndr_stats = calc . sndr_sfdr (psd_ss , f_ss , fs , nfft , leak = leak , full_scale = full_scale )
233118
234- harm_stats = find_harmonics (
119+ harm_stats = calc . find_harmonics (
235120 psd_ss ,
236121 f_ss ,
237122 nfft ,
@@ -247,7 +132,7 @@ def plot_spectrum(
247132 xmin = 0 if single_sided else - fs / 2e6
248133
249134 if not no_plot :
250- plt_str = get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
135+ plt_str = calc . get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
251136 fig , ax = plt .subplots (figsize = (15 , 8 ))
252137 ax .plot (freq / xscale , psd_out )
253138 ax .set_ylabel (f"Power Spectrum ({ yunits } )" , fontsize = 18 )
@@ -316,39 +201,6 @@ def plot_spectrum(
316201 return (freq , psd_out , stats )
317202
318203
319- def get_plot_string (stats , full_scale , fs , nfft , window , xscale = 1e6 , fscale = "MHz" ):
320- """Generate plot string from stats dict."""
321-
322- plt_str = "==== FFT ====\n "
323- plt_str += f"NFFT = { nfft } \n "
324- plt_str += f"fbin = { round (fs / nfft / 1e3 , 2 )} kHz\n "
325- plt_str += f"window = { window } \n "
326- plt_str += "\n "
327- plt_str += "==== Signal ====\n "
328- plt_str += f"FullScale = { full_scale } dB\n "
329- plt_str += f"Psig = { stats ['sig' ]['dBFS' ]} dBFS ({ stats ['sig' ]['dB' ]} dB)\n "
330- plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ xscale , 2 )} { fscale } \n "
331- plt_str += f"fsamp = { round (fs / xscale , 2 )} { fscale } \n "
332- plt_str += "\n "
333- plt_str += "==== SNDR/SFDR ====\n "
334- plt_str += f"ENOB = { stats ['enob' ]['bits' ]} bits\n "
335- plt_str += f"SNDR = { stats ['sndr' ]['dBFS' ]} dBFS ({ stats ['sndr' ]['dBc' ]} dBc)\n "
336- plt_str += f"SFDR = { stats ['sfdr' ]['dBFS' ]} dBFS ({ stats ['sfdr' ]['dBc' ]} dBc)\n "
337- plt_str += f"Pspur = { stats ['spur' ]['dBFS' ]} dBFS\n "
338- plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ xscale , 2 )} { fscale } \n "
339- plt_str += f"Noise Floor = { stats ['noise' ]['dBHz' ]} dBFS\n "
340- plt_str += f"NSD = { stats ['noise' ]['NSD' ]} dBFS\n "
341- plt_str += "\n "
342- plt_str += "==== Harmonics ====\n "
343-
344- for hindex , hdata in stats ["harm" ].items ():
345- plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} { fscale } \n "
346-
347- plt_str += "\n "
348-
349- return plt_str
350-
351-
352204def analyze (
353205 data ,
354206 nfft ,
@@ -377,4 +229,4 @@ def analyze(
377229 fscale = fscale ,
378230 )
379231
380- return (freq , spectrum , stats )
232+ return (freq , spectrum , stats )
0 commit comments