@@ -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+
3956def 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 ))
0 commit comments