@@ -27,7 +27,8 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
2727 """Get SNDR and SFDR."""
2828
2929 # Zero the DC bin
30- spectrum [0 ] = 0
30+ for i in range (0 , leak + 1 ):
31+ spectrum [i ] = 0
3132 bin_sig = np .argmax (spectrum )
3233 psig = sum (spectrum [i ] for i in range (bin_sig - leak , bin_sig + leak + 1 ))
3334 spectrum_n = spectrum
@@ -80,7 +81,7 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
8081 return stats
8182
8283
83- def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 ):
84+ def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 , fscale = 1e6 ):
8485 """Get the harmonic contents of the data."""
8586 harm_stats = {"harm" : {}}
8687 harm_index = 2
@@ -110,7 +111,7 @@ def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20):
110111
111112 harm_stats ["harm" ][harm_index ]["bin" ] = bin_harm_max
112113 harm_stats ["harm" ][harm_index ]["power" ] = pwr_max
113- harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / 1e6 , 1 )
114+ harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / fscale , 1 )
114115 harm_stats ["harm" ][harm_index ]["dBc" ] = dBW (pwr_max / psig )
115116 harm_stats ["harm" ][harm_index ]["dB" ] = dBW (pwr_max )
116117
@@ -161,6 +162,7 @@ def plot_spectrum(
161162 no_plot = False ,
162163 yaxis = "power" ,
163164 single_sided = True ,
165+ fscale = "MHz" ,
164166):
165167 """Plot Power Spectrum for input signal."""
166168 wsize = data .size
@@ -169,10 +171,21 @@ def plot_spectrum(
169171 "hanning" : np .hanning (wsize ),
170172 }
171173
174+ fscalar = {
175+ "Hz" : 1 ,
176+ "kHz" : 1e3 ,
177+ "MHz" : 1e6 ,
178+ "GHz" : 1e9 ,
179+ }
180+
172181 if window not in windows :
173182 print (f"WARNING: { window } not implemented. Defaulting to 'rectangular'." )
174183 window = "rectangular"
175184
185+ if fscale not in fscalar :
186+ print (f"WARNING: { fscale } not implemented. Defaulting to 'MHz'." )
187+ fscale = "MHz"
188+
176189 wscale = {
177190 "rectangular" : 1.0 ,
178191 "hanning" : 1.633 ,
@@ -193,6 +206,7 @@ def plot_spectrum(
193206 lut_key = yaxis .lower ()
194207 scalar = yaxis_lut [lut_key ][0 ]
195208 yunits = yaxis_lut [lut_key ][1 ]
209+ xscale = fscalar [fscale ]
196210
197211 psd_out = 10 * np .log10 (pwr ) - scalar
198212 if lut_key in ["magnitude" ]:
@@ -207,6 +221,7 @@ def plot_spectrum(
207221 )
208222
209223 sndr_stats = sndr_sfdr (psd_ss , f_ss , fs , nfft , leak = leak , full_scale = full_scale )
224+
210225 harm_stats = find_harmonics (
211226 psd_ss ,
212227 f_ss ,
@@ -215,20 +230,21 @@ def plot_spectrum(
215230 sndr_stats ["sig" ]["power" ],
216231 harms = harmonics ,
217232 leak = leak ,
233+ fscale = xscale ,
218234 )
219235
220236 stats = {** sndr_stats , ** harm_stats }
221237
222238 xmin = 0 if single_sided else - fs / 2e6
223239
224240 if not no_plot :
225- plt_str = get_plot_string (stats , full_scale , fs , nfft , window )
241+ plt_str = get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
226242 fig , ax = plt .subplots (figsize = (15 , 8 ))
227- ax .plot (freq / 1e6 , psd_out )
243+ ax .plot (freq / xscale , psd_out )
228244 ax .set_ylabel (f"Power Spectrum ({ yunits } )" , fontsize = 18 )
229- ax .set_xlabel ("Frequency (MHz )" , fontsize = 16 )
245+ ax .set_xlabel (f "Frequency ({ fscale } )" , fontsize = 16 )
230246 ax .set_title ("Output Power Spectrum" , fontsize = 16 )
231- ax .set_xlim ([xmin , fs / 2e6 ])
247+ ax .set_xlim ([xmin , fs / ( 2 * xscale ) ])
232248 ax .set_ylim ([1.1 * min (psd_out ), 1 ])
233249
234250 ax .annotate (
@@ -291,7 +307,7 @@ def plot_spectrum(
291307 return (freq , psd_out , stats )
292308
293309
294- def get_plot_string (stats , full_scale , fs , nfft , window ):
310+ def get_plot_string (stats , full_scale , fs , nfft , window , xscale = 1e6 , fscale = "MHz" ):
295311 """Generate plot string from stats dict."""
296312
297313 plt_str = "==== FFT ====\n "
@@ -302,22 +318,22 @@ def get_plot_string(stats, full_scale, fs, nfft, window):
302318 plt_str += "==== Signal ====\n "
303319 plt_str += f"FullScale = { full_scale } dB\n "
304320 plt_str += f"Psig = { stats ['sig' ]['dBFS' ]} dBFS ({ stats ['sig' ]['dB' ]} dB)\n "
305- plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ 1e6 , 2 )} MHz \n "
306- plt_str += f"fsamp = { round (fs / 1e6 , 2 )} MHz \n "
321+ plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ xscale , 2 )} { fscale } \n "
322+ plt_str += f"fsamp = { round (fs / xscale , 2 )} { fscale } \n "
307323 plt_str += "\n "
308324 plt_str += "==== SNDR/SFDR ====\n "
309325 plt_str += f"ENOB = { stats ['enob' ]['bits' ]} bits\n "
310326 plt_str += f"SNDR = { stats ['sndr' ]['dBFS' ]} dBFS ({ stats ['sndr' ]['dBc' ]} dBc)\n "
311327 plt_str += f"SFDR = { stats ['sfdr' ]['dBFS' ]} dBFS ({ stats ['sfdr' ]['dBc' ]} dBc)\n "
312328 plt_str += f"Pspur = { stats ['spur' ]['dBFS' ]} dBFS\n "
313- plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ 1e6 , 2 )} MHz \n "
329+ plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ xscale , 2 )} { fscale } \n "
314330 plt_str += f"Noise Floor = { stats ['noise' ]['dBHz' ]} dBFS\n "
315331 plt_str += f"NSD = { stats ['noise' ]['NSD' ]} dBFS\n "
316332 plt_str += "\n "
317333 plt_str += "==== Harmonics ====\n "
318334
319335 for hindex , hdata in stats ["harm" ].items ():
320- plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} MHz \n "
336+ plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} { fscale } \n "
321337
322338 plt_str += "\n "
323339
@@ -335,6 +351,7 @@ def analyze(
335351 no_plot = False ,
336352 yaxis = "fullscale" ,
337353 single_sided = True ,
354+ fscale = "MHz" ,
338355):
339356 """Perform spectral analysis on input waveform."""
340357 (freq , spectrum , stats ) = plot_spectrum (
@@ -348,6 +365,7 @@ def analyze(
348365 no_plot = no_plot ,
349366 yaxis = yaxis ,
350367 single_sided = single_sided ,
368+ fscale = fscale ,
351369 )
352370
353371 return (freq , spectrum , stats )
0 commit comments