plot-cal-data-fits.py
Initially, the script processes the command line and then invokes the
main
function passing the specified command arguments or their
defaults.
from argparse import ArgumentParser import json import numpy as np import matplotlib import matplotlib.pyplot as plt matplotlib.use('Agg') from dyadic.splot import init_style DS_SLOPE_VS_FREQ = { '25': { 'Freq': [0.00, 454.55, 659.09, 818.18, 1011.36, 1397.73, 1727.27, 2011.36, 2272.73, 2511.36, 2772.73, 2988.64, 3227.27, 3534.09, 3840.91, 4136.36, 4454.55, 4818.18, 5193.18, 5568.18, 6000.00], 'Slope': [29.46, 29.45, 29.36, 29.33, 29.37, 29.49, 29.61, 29.72, 29.79, 29.81, 29.86, 29.95, 30.06, 30.19, 30.30, 30.40, 30.51, 30.62, 30.75, 30.87, 31.01] }, '85': { 'Freq': [0.00, 454.55, 613.64, 795.45, 1000.00, 1306.82, 1613.64, 1897.73, 2170.45, 2340.91, 2579.55, 2806.82, 3045.45, 3340.91, 3613.64, 3897.73, 4193.18, 4625.00, 5045.45, 5409.09, 6000.00], 'Slope': [28.84, 28.82, 28.73, 28.68, 28.72, 28.82, 28.93, 29.03, 29.11, 29.13, 29.11, 29.15, 29.23, 29.31, 29.35, 29.34, 29.30, 29.21, 29.11, 29.02, 28.89] }, '-40': { 'Freq': [0.00, 443.18, 590.91, 772.73, 965.91, 1329.55, 1659.09, 2000.00, 2238.64, 2477.27, 2670.45, 2886.36, 3170.45, 3397.73, 3659.09, 4000.00, 4363.64, 4818.18, 5318.18, 5772.73, 6000.00], 'Slope': [29.41, 29.41, 29.33, 29.28, 29.30, 29.41, 29.53, 29.65, 29.71, 29.73, 29.75, 29.82, 29.94, 30.03, 30.09, 30.11, 30.10, 30.07, 30.03, 30.00, 29.98] } } DS_INTERCEPT_VS_FREQ = { '25': { 'Freq': [0.00, 444.02, 637.57, 876.66, 1559.77, 2026.57, 2288.43, 2527.51, 2823.53, 3153.70, 3552.18, 3973.43, 4371.92, 4872.87, 5282.73, 5612.90, 5851.99, 6000.00], 'Intercept': [-86.15, -86.18, -86.42, -86.42, -85.53, -84.89, -84.51, -84.20, -83.56, -82.77, -81.72, -80.49, -79.30, -77.66, -76.36, -75.31, -74.49, -73.98] }, '85': { 'Freq': [0.00, 465.91, 715.91, 886.36, 1204.55, 1590.91, 2011.36, 2318.18, 2556.82, 2750.00, 3147.73, 3420.45, 3761.36, 4147.73, 4534.09, 4909.09, 5545.45, 6000.00], 'Intercept': [-87.34, -87.41, -87.68, -87.65, -87.27, -86.73, -86.15, -85.70, -85.43, -85.06, -84.10, -83.42, -82.64, -81.85, -81.10, -80.35, -79.12, -78.17] }, '-40': { 'Freq': [0.00, 454.55, 670.45, 863.64, 1272.73, 1602.27, 2011.36, 2227.27, 2488.64, 2750.00, 3045.45, 3306.82, 3636.36, 4011.36, 4306.82, 4840.91, 5420.45, 6000.00], 'Intercept': [-86.05, -86.18, -86.45, -86.45, -85.98, -85.57, -85.06, -84.78, -84.61, -84.27, -83.76, -83.25, -82.57, -81.65, -80.83, -79.40, -77.80, -76.09] } } frequencies = ['250.0', '500.0', '750.0', '1000', '1250', '1500', '1750', '2000', '2250', '2500', '2750', '3000', '3250', '3500', '3750', '4000', '4250'] def main(infiles, plotfile, outfile, extrapolate): linear_fits = [] for infile in infiles: with open(infile) as fd: d = json.load(fd) linear_fits.append(d['linear_fits']) # ------------------------------------------------------------ # # plotfile: # # Plot linear fit slopes and log intercepts as functions of # RF input frequency. If the 'extrapolate' flag is True # then compute extrapolations of the slope and intercept # up to 6000 MHz and down to 0 MHz. # init_style() freq_array = np.array([float(f) for f in frequencies]) fig = plt.figure(num=None, figsize=(8.0, 4.0), dpi=72) ax1 = fig.add_subplot(1, 2, 1) ax1.set_ylim(28.0, 33.0) ax1.set_xlim(0.0, 6000.0) ax1.set_ylabel('Slope (mV/dB)') ax1.set_xlabel('Frequency (MHz)') ax2 = fig.add_subplot(1, 2, 2) ax2.set_ylim(-90.0, -70.0) ax2.set_xlim(0.0, 6000.0) ax2.set_ylabel('Intercept (dBm)') ax2.set_xlabel('Frequency (MHz)') # Average slopes and intercepts across the specified cal. data sets. slopes = np.zeros(len(freq_array)) intercepts = np.zeros(len(freq_array)) for fit in linear_fits: slopes += np.array([v[0] for v in fit.values()]) intercepts += np.array([-v[1]/v[0] for v in fit.values()]) avg_slopes = slopes / len(linear_fits) avg_intercepts = intercepts / len(linear_fits) ax1.plot(freq_array, avg_slopes, label='measured') ax2.plot(freq_array, avg_intercepts, label='measured') # Plot the data sheet cal. graphs for comparison freq_array = np.array(DS_SLOPE_VS_FREQ['25']['Freq']) slopes_25 = DS_SLOPE_VS_FREQ['25']['Slope'] ax1.plot(freq_array, slopes_25, linestyle='dashed', label='datasheet, 25C') freq_array = np.array(DS_SLOPE_VS_FREQ['-40']['Freq']) slopes_40 = DS_SLOPE_VS_FREQ['-40']['Slope'] ax1.plot(freq_array, slopes_40, linestyle='dashed', label='datasheet, -40C') freq_array = np.array(DS_SLOPE_VS_FREQ['85']['Freq']) slopes_85 = DS_SLOPE_VS_FREQ['85']['Slope'] ax1.plot(freq_array, slopes_85, linestyle='dashed', label='datasheet, 85C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['25']['Freq']) intercepts_25 = DS_INTERCEPT_VS_FREQ['25']['Intercept'] ax2.plot(freq_array, intercepts_25, linestyle='dashed', label='datasheet, 25C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['-40']['Freq']) intercepts_40 = DS_INTERCEPT_VS_FREQ['-40']['Intercept'] ax2.plot(freq_array, intercepts_40, linestyle='dashed', label='datasheet, -40C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['85']['Freq']) intercepts_85 = DS_INTERCEPT_VS_FREQ['85']['Intercept'] ax2.plot(freq_array, intercepts_85, linestyle='dashed', label='datasheet, 85C') if extrapolate: ext_freqs = np.append(np.array([float(f) for f in frequencies])[:-2], 6000.0) ext_freqs = np.insert(ext_freqs, 0, 0.0) freq_array = np.array(DS_SLOPE_VS_FREQ['25']['Freq']) delta_slope = ((slopes_25[-1] - slopes_25[14]) / (freq_array[-1] - freq_array[14])) * (ext_freqs[-1] - ext_freqs[-2]) ext_slopes = np.append(avg_slopes[:-2], avg_slopes[-3] + delta_slope) ext_slopes = np.insert(ext_slopes, 0, ext_slopes[0]) freq_array = np.array(DS_INTERCEPT_VS_FREQ['25']['Freq']) delta_intercept = ((intercepts_25[-1] - intercepts_25[10]) / (freq_array[-1] - freq_array[10])) * (ext_freqs[-1] - ext_freqs[-2]) ext_intercepts = np.append(avg_intercepts[:-2], avg_intercepts[-3] + delta_intercept) ext_intercepts = np.insert(ext_intercepts, 0, ext_intercepts[0]) ax1.plot(ext_freqs, ext_slopes, linestyle='dotted', label='extrapolated') ax2.plot(ext_freqs, ext_intercepts, linestyle='dotted', label='extrapolated') cal_data = {} for freq, slope, intercept in zip(ext_freqs, ext_slopes, ext_intercepts): cal_data['{:.1f}'.format(freq)] = [ slope, intercept ] with open(outfile, 'w') as fd: json.dump(cal_data, fd) ax1.legend(loc='upper left') ax2.legend(loc='upper left') fig.tight_layout() plt.savefig(plotfile) if __name__ == '__main__': defaultInfile = ['ltc5582-unit1-cal-data.json'] defaultPlotfile = 'static/tmp-ltc5582-unit1-cal-fits.svg' defaultOutfile = 'ltc5582-unit1-extrapolated-cal-data.json' parser = ArgumentParser(description= '''Plot LTC5582 detector calibration data fits.''') parser.add_argument("-E", "--extrapolate", action='store_true', help="""Extrapolate cal. data fits linearly to 6 GHz.""") parser.add_argument("-I", "--infiles", nargs='+', default=defaultInfile, help="""Input file(s) containing calibration data. Default: {}""".format(defaultInfile)) parser.add_argument("-O", "--outfile", default=defaultOutfile, help="""Output file for extrapolated cal. data. Default: {}""".format(defaultOutfile)) parser.add_argument("-P", "--plotfile", default=defaultPlotfile, help="""Output file for slope/intercept plots. Default: {}""".format(defaultPlotfile)) args = parser.parse_args() main(args.infiles, args.plotfile, args.outfile, args.extrapolate)
The main
function: <<main>>
The main
function proceeds in the following steps:
Read the calibration measurements and associated linear fit parameters acquired using the measure-cal-data.py script.
Produces plots of the linear fit slope and intercept parameters.
If required, extrapolate the linear fit parameters up to 6 GHz. The extrapolated points are also included in the parameter plots.
Save the parameter plots and, if required, the updated, extrapolated linear fit parameters.
def main(infiles, plotfile, outfile, extrapolate): linear_fits = [] for infile in infiles: with open(infile) as fd: d = json.load(fd) linear_fits.append(d['linear_fits']) <<plot-fit-parameters>> if extrapolate: <<extrapolate-fit-parameters>> ax1.legend(loc='upper left') ax2.legend(loc='upper left') fig.tight_layout() plt.savefig(plotfile)
Plot linear fit parameters: <<plot-fit-parameters>>
Plot the linear fit slopes and intercepts as functions of the signal
input frequency. If more than one set of calibration data are
specified the fit parameters will be averaged. For comparison
purposes, slope and intercept data taken from the LTC5582 data sheet
are also shown in the same plot (as dashed curves). The data sheet
slope and intercept data is contained in the DS_SLOPE_VS_FREQ
and
DS_INTERCEPT_VS_FREQ
global variables.
Example slope and intercept plots are shown in Figure 6.
# ------------------------------------------------------------ # # plotfile: # # Plot linear fit slopes and log intercepts as functions of # RF input frequency. If the 'extrapolate' flag is True # then compute extrapolations of the slope and intercept # up to 6000 MHz and down to 0 MHz. # init_style() freq_array = np.array([float(f) for f in frequencies]) fig = plt.figure(num=None, figsize=(8.0, 4.0), dpi=72) ax1 = fig.add_subplot(1, 2, 1) ax1.set_ylim(28.0, 33.0) ax1.set_xlim(0.0, 6000.0) ax1.set_ylabel('Slope (mV/dB)') ax1.set_xlabel('Frequency (MHz)') ax2 = fig.add_subplot(1, 2, 2) ax2.set_ylim(-90.0, -70.0) ax2.set_xlim(0.0, 6000.0) ax2.set_ylabel('Intercept (dBm)') ax2.set_xlabel('Frequency (MHz)') # Average slopes and intercepts across the specified cal. data sets. slopes = np.zeros(len(freq_array)) intercepts = np.zeros(len(freq_array)) for fit in linear_fits: slopes += np.array([v[0] for v in fit.values()]) intercepts += np.array([-v[1]/v[0] for v in fit.values()]) avg_slopes = slopes / len(linear_fits) avg_intercepts = intercepts / len(linear_fits) ax1.plot(freq_array, avg_slopes, label='measured') ax2.plot(freq_array, avg_intercepts, label='measured') # Plot the data sheet cal. graphs for comparison freq_array = np.array(DS_SLOPE_VS_FREQ['25']['Freq']) slopes_25 = DS_SLOPE_VS_FREQ['25']['Slope'] ax1.plot(freq_array, slopes_25, linestyle='dashed', label='datasheet, 25C') freq_array = np.array(DS_SLOPE_VS_FREQ['-40']['Freq']) slopes_40 = DS_SLOPE_VS_FREQ['-40']['Slope'] ax1.plot(freq_array, slopes_40, linestyle='dashed', label='datasheet, -40C') freq_array = np.array(DS_SLOPE_VS_FREQ['85']['Freq']) slopes_85 = DS_SLOPE_VS_FREQ['85']['Slope'] ax1.plot(freq_array, slopes_85, linestyle='dashed', label='datasheet, 85C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['25']['Freq']) intercepts_25 = DS_INTERCEPT_VS_FREQ['25']['Intercept'] ax2.plot(freq_array, intercepts_25, linestyle='dashed', label='datasheet, 25C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['-40']['Freq']) intercepts_40 = DS_INTERCEPT_VS_FREQ['-40']['Intercept'] ax2.plot(freq_array, intercepts_40, linestyle='dashed', label='datasheet, -40C') freq_array = np.array(DS_INTERCEPT_VS_FREQ['85']['Freq']) intercepts_85 = DS_INTERCEPT_VS_FREQ['85']['Intercept'] ax2.plot(freq_array, intercepts_85, linestyle='dashed', label='datasheet, 85C')
Extrapolate fit parameters: <<extrapolate-fit-parameters>>
If extrapolation of the fit parameters is required (as indicated by
the script extrapolate flag) extra slope and intercept parameter
points are calculated using the LTC5582 data sheet slope and
intercept curves as guides. Data points taken from the data sheet
curves are contained in the DS_SLOPE_VS_FREQ
and
DS_INTERCEPT_VS_FREQ
global variables.
Parameter values are extrapolated upward by extending the measured value using the slopes of the curves taken from data sheet values. Parameter values are extrapolated downward (to 0 Hz) by duplicating the lowest frequency measured values.
ext_freqs = np.append(np.array([float(f) for f in frequencies])[:-2], 6000.0) ext_freqs = np.insert(ext_freqs, 0, 0.0) freq_array = np.array(DS_SLOPE_VS_FREQ['25']['Freq']) delta_slope = ((slopes_25[-1] - slopes_25[14]) / (freq_array[-1] - freq_array[14])) * (ext_freqs[-1] - ext_freqs[-2]) ext_slopes = np.append(avg_slopes[:-2], avg_slopes[-3] + delta_slope) ext_slopes = np.insert(ext_slopes, 0, ext_slopes[0]) freq_array = np.array(DS_INTERCEPT_VS_FREQ['25']['Freq']) delta_intercept = ((intercepts_25[-1] - intercepts_25[10]) / (freq_array[-1] - freq_array[10])) * (ext_freqs[-1] - ext_freqs[-2]) ext_intercepts = np.append(avg_intercepts[:-2], avg_intercepts[-3] + delta_intercept) ext_intercepts = np.insert(ext_intercepts, 0, ext_intercepts[0]) ax1.plot(ext_freqs, ext_slopes, linestyle='dotted', label='extrapolated') ax2.plot(ext_freqs, ext_intercepts, linestyle='dotted', label='extrapolated') cal_data = {} for freq, slope, intercept in zip(ext_freqs, ext_slopes, ext_intercepts): cal_data['{:.1f}'.format(freq)] = [ slope, intercept ] with open(outfile, 'w') as fd: json.dump(cal_data, fd)
Globals: <<globals>>
The DS_SLOPE_VS_FREQ
and DS_INTERCEPT_VS_FREQ
global variables
contain data taken from the LTC5582 data sheet. They are:
DS_SLOPE_VS_FREQ
-
Values taken from the LTC5582 data sheet 'Slope vs Frequency' graph.
DS_INTERCEPT_VS_FREQ
-
Values taken from the LTC5582 data sheet ('Logarithmic Intercept vs Frequency' graph.
DS_SLOPE_VS_FREQ = { '25': { 'Freq': [0.00, 454.55, 659.09, 818.18, 1011.36, 1397.73, 1727.27, 2011.36, 2272.73, 2511.36, 2772.73, 2988.64, 3227.27, 3534.09, 3840.91, 4136.36, 4454.55, 4818.18, 5193.18, 5568.18, 6000.00], 'Slope': [29.46, 29.45, 29.36, 29.33, 29.37, 29.49, 29.61, 29.72, 29.79, 29.81, 29.86, 29.95, 30.06, 30.19, 30.30, 30.40, 30.51, 30.62, 30.75, 30.87, 31.01] }, '85': { 'Freq': [0.00, 454.55, 613.64, 795.45, 1000.00, 1306.82, 1613.64, 1897.73, 2170.45, 2340.91, 2579.55, 2806.82, 3045.45, 3340.91, 3613.64, 3897.73, 4193.18, 4625.00, 5045.45, 5409.09, 6000.00], 'Slope': [28.84, 28.82, 28.73, 28.68, 28.72, 28.82, 28.93, 29.03, 29.11, 29.13, 29.11, 29.15, 29.23, 29.31, 29.35, 29.34, 29.30, 29.21, 29.11, 29.02, 28.89] }, '-40': { 'Freq': [0.00, 443.18, 590.91, 772.73, 965.91, 1329.55, 1659.09, 2000.00, 2238.64, 2477.27, 2670.45, 2886.36, 3170.45, 3397.73, 3659.09, 4000.00, 4363.64, 4818.18, 5318.18, 5772.73, 6000.00], 'Slope': [29.41, 29.41, 29.33, 29.28, 29.30, 29.41, 29.53, 29.65, 29.71, 29.73, 29.75, 29.82, 29.94, 30.03, 30.09, 30.11, 30.10, 30.07, 30.03, 30.00, 29.98] } } DS_INTERCEPT_VS_FREQ = { '25': { 'Freq': [0.00, 444.02, 637.57, 876.66, 1559.77, 2026.57, 2288.43, 2527.51, 2823.53, 3153.70, 3552.18, 3973.43, 4371.92, 4872.87, 5282.73, 5612.90, 5851.99, 6000.00], 'Intercept': [-86.15, -86.18, -86.42, -86.42, -85.53, -84.89, -84.51, -84.20, -83.56, -82.77, -81.72, -80.49, -79.30, -77.66, -76.36, -75.31, -74.49, -73.98] }, '85': { 'Freq': [0.00, 465.91, 715.91, 886.36, 1204.55, 1590.91, 2011.36, 2318.18, 2556.82, 2750.00, 3147.73, 3420.45, 3761.36, 4147.73, 4534.09, 4909.09, 5545.45, 6000.00], 'Intercept': [-87.34, -87.41, -87.68, -87.65, -87.27, -86.73, -86.15, -85.70, -85.43, -85.06, -84.10, -83.42, -82.64, -81.85, -81.10, -80.35, -79.12, -78.17] }, '-40': { 'Freq': [0.00, 454.55, 670.45, 863.64, 1272.73, 1602.27, 2011.36, 2227.27, 2488.64, 2750.00, 3045.45, 3306.82, 3636.36, 4011.36, 4306.82, 4840.91, 5420.45, 6000.00], 'Intercept': [-86.05, -86.18, -86.45, -86.45, -85.98, -85.57, -85.06, -84.78, -84.61, -84.27, -83.76, -83.25, -82.57, -81.65, -80.83, -79.40, -77.80, -76.09] } } frequencies = ['250.0', '500.0', '750.0', '1000', '1250', '1500', '1750', '2000', '2250', '2500', '2750', '3000', '3250', '3500', '3750', '4000', '4250']