Skip to content

Instantly share code, notes, and snippets.

@0x9900
Last active June 16, 2024 17:49
Show Gist options
  • Save 0x9900/6e2279367e591122cc92d7406b9274b0 to your computer and use it in GitHub Desktop.
Save 0x9900/6e2279367e591122cc92d7406b9274b0 to your computer and use it in GitHub Desktop.
Graph SWR smith chart and return loss
#!/usr/bin/env python3
#
# BSD 3-Clause License
#
# Copyright (c) 2023-2024 Fred W6BSD
# All rights reserved.
#
#
import argparse
import logging
import os
import re
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
import skrf as rf
__version__ = "0.0.1"
METADATA = {
"Author": "Fred W6BSD",
"Software": "RigExpert.py",
"Version": __version__,
}
DPI = 100
LINE_COLOR = "yellow"
SWR_COLOR = "cyan"
LIGHTGRAY = "#ababab"
my_params = {
"axes.edgecolor": LIGHTGRAY,
"axes.grid": True,
"axes.grid.which": "both",
"axes.labelsize": 14,
"axes.linewidth": 1.5,
"axes.spines.bottom": True,
"axes.spines.left": True,
"axes.spines.right": False,
"axes.spines.top": False,
"figure.dpi": DPI,
"grid.alpha": 0.75,
"grid.color": LIGHTGRAY,
"grid.linewidth": .33,
"legend.fontsize": 12,
"lines.linewidth": 1.5,
"xtick.color": LIGHTGRAY,
"xtick.labelcolor": LIGHTGRAY,
"xtick.labelsize": 12,
"ytick.color": LIGHTGRAY,
"ytick.labelcolor": LIGHTGRAY,
"ytick.labelsize": 12,
}
plt.style.use("dark_background")
plt.rcParams.update(my_params)
BANDS = (
(14000, 14350, '20m'),
(7000, 7300, '40m'),
(10100, 10150, '30m'),
(3500, 4000, '80m'),
(21000, 21450, '15m'),
(18068, 18168, '17m'),
(28000, 29700, '10m'),
(50000, 54000, '6m'),
(24890, 24990, '12m'),
(1800, 2000, '160m'),
(144000, 148000, '2m'),
(5258, 5450, '60m'),
(420000, 450000, '0.70m'),
(219000, 225000, '1.25m'),
(1240000, 1300000, '0.23m'),
(10000000, 10500000, '0.02m'),
)
def slugify(text):
return re.sub(r'[\W_]+', '-', text.lower())
def read_s1p(filename, f_range=None):
try:
network = rf.Network(filename)
except NotImplementedError as err:
logging.info('Error importing %s', filename)
logging.info('%s - Choose the Touchstone [S,RI] format when exporting your data', err)
raise SystemExit('Import error') from None
# Often the RigExpert export a wrong last value
# Remove the last value
network = network[0:-1]
if f_range:
start = np.argmin(np.abs(network.frequency.f - f_range[0]))
stop = np.argmin(np.abs(network.frequency.f - f_range[1]))
network = network[start:stop]
network.frequency.unit = 'MHz'
return network
class DrawAll:
def __init__(self, filename, title, f_range):
self.filename = filename
self.title = title
self.dut = read_s1p(filename, f_range)
fig, self.axs = plt.subplots(2, 2, figsize=(13, 7))
fig.suptitle(title)
def smith(self):
self.dut.s11.plot_s_smith(ax=self.axs[0, 0], show_legend=True, color=LINE_COLOR,
label="Complex Impedence")
def vswr(self):
self.dut.s11.plot_s_vswr(ax=self.axs[0, 1], color=LINE_COLOR, label='VSWR')
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.axs[0, 1].set_xlim(fmin, fmax)
self.axs[0, 1].set_ylabel(r'$\rho$')
max_vswr = (1+self.dut.s_mag.max())/(1-self.dut.s_mag.max())
self.axs[0, 1].set_ylim(1, 3 if max_vswr < 3 else max_vswr * 1.2 if max_vswr < 10 else 10)
self.axs[0, 1].axhline(y=2, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
self.axs[0, 1].axhline(y=3, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
for low, high, _l in BANDS:
self.axs[0, 1].axvspan(low*1000, high*1000, facecolor='cyan', alpha=0.15)
self.axs[0, 1].legend(loc='upper center')
def return_loss(self):
self.dut.s11.plot_s_db(ax=self.axs[1, 0], color=LINE_COLOR, label="Return Loss")
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.axs[1, 0].set_xlim(fmin, fmax)
self.axs[1, 0].set_ylim(top=0)
self.axs[1, 0].set_ylabel(r'$\Gamma$')
self.axs[1, 0].axhline(y=-6, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
self.axs[1, 0].axhline(y=-10, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
for low, high, _ in BANDS:
self.axs[1, 0].axvspan(low*1000, high*1000, facecolor='cyan', alpha=0.15)
self.axs[1, 0].legend(loc='upper center')
def phase(self):
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.dut.s11.plot_s_deg(ax=self.axs[1, 1], color=LINE_COLOR, label="Phase")
self.axs[1, 1].set_xlim(fmin, fmax)
self.axs[1, 1].set_ylim(bottom=-180, top=180)
self.axs[1, 1].set_ylabel(r'$\phi$')
for low, high, _ in BANDS:
self.axs[1, 1].axvspan(low*1000, high*1000, facecolor='lightgray', alpha=0.15)
def graph(self):
self.smith()
self.vswr()
self.return_loss()
self.phase()
dname, fname = os.path.split(self.filename)
fname, _ = os.path.splitext(fname)
image_name = os.path.join(dname, f'{slugify(self.title)}-{fname}-all.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.tight_layout()
plt.savefig(image_name, transparent=False, metadata=METADATA)
class Draw:
def __init__(self, filename, title, f_range):
self.filename = filename
self.title = title
self.dut = read_s1p(filename, f_range)
fig = plt.figure(figsize=(13, 7))
self.ax = fig.gca()
fig.suptitle(title)
self.dname, fname = os.path.split(self.filename)
self.fname, _ = os.path.splitext(fname)
def smith(self):
self.dut.s11.plot_s_smith(ax=self.ax, show_legend=True, color=LINE_COLOR,
label='Smith Chart')
image_name = os.path.join(self.dname, f'{slugify(self.title)}-{self.fname}-smith.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.savefig(image_name, transparent=False, metadata=METADATA)
def vswr(self):
self.dut.s11.plot_s_vswr(ax=self.ax, color=LINE_COLOR, label='VSWR')
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.ax.set_xlim(fmin, fmax)
self.ax.set_ylabel(r'$\rho$')
max_vswr = (1+self.dut.s_mag.max())/(1-self.dut.s_mag.max())
self.ax.set_ylim(1, 3 if max_vswr < 3 else max_vswr * 1.2 if max_vswr < 10 else 10)
self.ax.axhline(y=2, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
self.ax.axhline(y=3, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
for low, high, _l in BANDS:
self.ax.axvspan(low*1000, high*1000, facecolor='cyan', alpha=0.15)
image_name = os.path.join(self.dname, f'{slugify(self.title)}-{self.fname}-vswr.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.legend(loc='upper center')
plt.savefig(image_name, transparent=False, metadata=METADATA)
def return_loss(self):
self.dut.s11.plot_s_db(ax=self.ax, color=LINE_COLOR, label="Return Loss")
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.ax.set_xlim(fmin, fmax)
self.ax.set_ylim(top=0)
self.ax.set_ylabel(r'$\Gamma$')
self.ax.axhline(y=-6, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
self.ax.axhline(y=-10, linewidth=1, zorder=9, color=SWR_COLOR, linestyle="-.")
for low, high, _ in BANDS:
self.ax.axvspan(low*1000, high*1000, facecolor='cyan', alpha=0.15)
image_name = os.path.join(self.dname, f'{slugify(self.title)}-{self.fname}-rloss.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.legend(loc='upper center')
plt.savefig(image_name, transparent=False, metadata=METADATA)
def phase(self):
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.dut.s11.plot_s_deg(ax=self.ax, color=LINE_COLOR, label="Phase")
self.ax.set_xlim(fmin, fmax)
self.ax.set_ylim(bottom=-90, top=90)
self.ax.set_ylabel(r'$\phi$')
for low, high, _ in BANDS:
self.ax.axvspan(low*1000, high*1000, facecolor='lightgray', alpha=0.15)
image_name = os.path.join(self.dname, f'{slugify(self.title)}-{self.fname}-phase.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.legend(loc='upper center')
plt.savefig(image_name, transparent=False, metadata=METADATA)
def impedance(self):
fmin = self.dut.frequency.f.min()
fmax = self.dut.frequency.f.max()
self.dut.s11.plot_z_re(ax=self.ax, color=LINE_COLOR, label="| Z |")
self.ax.set_xlim(fmin, fmax)
self.ax.set_ylabel(r'$\Omega$')
self.ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_number))
for low, high, _ in BANDS:
self.ax.axvspan(low*1000, high*1000, facecolor='lightgray', alpha=0.15)
image_name = os.path.join(self.dname, f'{slugify(self.title)}-{self.fname}-impedance.png')
logging.info(image_name)
METADATA["Title"] = self.title
METADATA["Creation Time"] = datetime.now().isoformat()
plt.legend(loc='upper center')
plt.savefig(image_name, transparent=False, metadata=METADATA)
def fmt_number(num, _):
for unit in ("", "K", "M", "G", "T"):
if abs(num) < 1000.0:
return f"{num:.1f}{unit}"
num /= 1000.0
return f"{num:.1f}P"
def type_range(parg):
start, stop = [float(x) * 10**6 for x in parg.split(':')]
return start, stop
def main():
parser = argparse.ArgumentParser(description="RigExpert")
parser.add_argument('-f', '--s1p-file', required=True)
parser.add_argument('-t', '--title', default='RigExpert')
parser.add_argument('-r', '--range', type=type_range, help='Frequency range start:stop in MHz')
d_group = parser.add_mutually_exclusive_group(required=True)
d_group.add_argument('-a', '--all', action="store_true", default=False)
d_group.add_argument('-s', '--vswr', action='store_true', default=False)
d_group.add_argument('-p', '--phase', action='store_true', default=False)
d_group.add_argument('-l', '--rloss', action='store_true', default=False)
d_group.add_argument('-w', '--smith', action='store_true', default=False)
d_group.add_argument('-z', '--impedance', action='store_true', default=False)
opts = parser.parse_args()
if opts.all:
draw = DrawAll(opts.s1p_file, opts.title, opts.range)
draw.graph()
return
draw = Draw(opts.s1p_file, opts.title, opts.range)
if opts.vswr:
draw.vswr()
elif opts.phase:
draw.phase()
elif opts.rloss:
draw.return_loss()
elif opts.smith:
draw.smith()
elif opts.impedance:
draw.impedance()
if __name__ == "__main__":
LOG_FORMAT = '%(asctime)s - %(lineno)d %(levelname)s - %(message)s'
logging.basicConfig(format=LOG_FORMAT, datefmt='%x %X', level=logging.INFO)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment