Skip to content

Commit

Permalink
Merge pull request #469 from NanoVNA-Saver/testing
Browse files Browse the repository at this point in the history
Release of 0.4.0
Enhanced through calibration
  • Loading branch information
zarath authored Apr 1, 2022
2 parents cbcf61a + 6c82ff6 commit 582b442
Show file tree
Hide file tree
Showing 25 changed files with 702 additions and 140 deletions.
21 changes: 13 additions & 8 deletions .github/workflows/release_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Checkout repository
uses: actions/checkout@v2
- name: Install python
run: |
sudo apt-get update
sudo apt install -y python3.9 python3-pip python3.9-venv \
python3.9-dev \
python3-pyqt5
- name: Install dependencies and pyinstall
run: |
python -m pip install --upgrade pip setuptools
python3.9 -m venv build
. build/bin/activate
python -m pip install pip==22.0.4 setuptools==60.10.0
pip install -r requirements.txt
pip install PyInstaller==4.5.1
pip install PyInstaller==4.10
- name: Build binary
run: |
. build/bin/activate
pyinstaller --onefile -n nanovna-saver nanovna-saver.py
- name: Archive production artifacts
uses: actions/upload-artifact@v1
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
python-version: 3.9
- name: Install dependencies and pyinstall
run: |
python -m pip install --upgrade pip setuptools
python -m pip install pip==22.0.3 setuptools==60.7.1
pip install -r requirements.txt
pip install PyInstaller==4.5.1
pip install PyInstaller==4.9
- name: Build binary
run: |
pyinstaller --onefile -n nanovna-saver nanovna-saver.py
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release_win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
architecture: ${{ matrix.arch }}
- name: Install dependencies and pyinstall
run: |
python -m pip install --upgrade pip setuptools
python -m pip install pip==22.0.3 setuptools==60.7.1
pip install -r requirements.txt
pip install PyInstaller==4.7
pip install PyInstaller==4.9
- name: Build binary
run: |
pyinstaller --onefile -n nanovna-saver.exe nanovna-saver.py
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

0.4.0
-----

- PA0JOZ Enhanced Response Correction
- Fix linux binary build
- Many bugfixes

v0.3.10
------

Expand Down
6 changes: 3 additions & 3 deletions NanoVNASaver/About.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

VERSION = "0.3.10"
VERSION = "0.4.0"
VERSION_URL = (
"https://raw.githubusercontent.com/"
"NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py")
Expand All @@ -26,7 +26,7 @@
INFO = f"""NanoVNASaver {VERSION}
Copyright (C) 2019, 2020 Rune B. Broberg
Copyright (C) 2020, 2021 NanoVNA-Saver Authors
Copyright (C) 2020ff NanoVNA-Saver Authors
This program comes with ABSOLUTELY NO WARRANTY
This program is licensed under the GNU General Public License version 3
Expand Down
105 changes: 67 additions & 38 deletions NanoVNASaver/Calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
(?P<openr>[-0-9Ee.]+) \s+ (?P<openi>[-0-9Ee.]+) \s+
(?P<loadr>[-0-9Ee.]+) \s+ (?P<loadi>[-0-9Ee.]+)(?: \s
(?P<throughr>[-0-9Ee.]+) \s+ (?P<throughi>[-0-9Ee.]+) \s+
(?P<thrureflr>[-0-9Ee.]+) \s+ (?P<thrurefli>[-0-9Ee.]+) \s+
(?P<isolationr>[-0-9Ee.]+) \s+ (?P<isolationi>[-0-9Ee.]+)
)?
""", re.VERBOSE)
Expand All @@ -55,16 +56,19 @@ def __init__(self):
"open": None,
"load": None,
"through": None,
"thrurefl": None,
"isolation": None,
# the frequence
"freq": 0,
# 1 Port
"e00": 0.0, # Directivity
"e11": 0.0, # Port match
"e11": 0.0, # Port1 match
"delta_e": 0.0, # Tracking
"e10e01": 0.0, # Forward Reflection Tracking
# 2 port
"e30": 0.0, # Port match
"e10e32": 0.0, # Transmission
"e30": 0.0, # Forward isolation
"e22": 0.0, # Port2 match
"e10e32": 0.0, # Forward transmission
}
super().__init__(data)

Expand All @@ -76,6 +80,7 @@ def __str__(self):
f' {d["load"].re} {d["load"].im}')
if d["through"] is not None:
s += (f' {d["through"].re} {d["through"].im}'
f' {d["thrurefl"].re} {d["thrurefl"].im}'
f' {d["isolation"].re} {d["isolation"].im}')
return s

Expand Down Expand Up @@ -115,14 +120,14 @@ def complete1port(self) -> bool:

def complete2port(self) -> bool:
for val in self.data.values():
for name in ("short", "open", "load", "through", "isolation"):
for name in ("short", "open", "load", "through", "thrurefl", "isolation"):
if val[name] is None:
return False
return any(self.data)


class Calibration:
CAL_NAMES = ("short", "open", "load", "through", "isolation",)
CAL_NAMES = ("short", "open", "load", "through", "thrurefl", "isolation",)
IDEAL_SHORT = complex(-1, 0)
IDEAL_OPEN = complex(1, 0)
IDEAL_LOAD = complex(0, 0)
Expand Down Expand Up @@ -180,6 +185,43 @@ def isValid1Port(self) -> bool:
def isValid2Port(self) -> bool:
return self.dataset.complete2port()

def _calc_port_1(self, freq: int, cal: CalData):
g1 = self.gamma_short(freq)
g2 = self.gamma_open(freq)
g3 = self.gamma_load(freq)

gm1 = cal["short"].z
gm2 = cal["open"].z
gm3 = cal["load"].z

denominator = (g1 * (g2 - g3) * gm1 +
g2 * g3 * gm2 - g2 * g3 * gm3 -
(g2 * gm2 - g3 * gm3) * g1)
cal["e00"] = - ((g2 * gm3 - g3 * gm3) * g1 * gm2 -
(g2 * g3 * gm2 - g2 * g3 * gm3 -
(g3 * gm2 - g2 * gm3) * g1) * gm1
) / denominator
cal["e11"] = ((g2 - g3) * gm1 - g1 * (gm2 - gm3) +
g3 * gm2 - g2 * gm3) / denominator
cal["delta_e"] = - ((g1 * (gm2 - gm3) - g2 * gm2 + g3 *
gm3) * gm1 + (g2 * gm3 - g3 * gm3) *
gm2) / denominator

def _calc_port_2(self, freq: int, cal: CalData):
gt = self.gamma_through(freq)

gm4 = cal["through"].z
gm5 = cal["thrurefl"].z
gm6 = cal["isolation"].z
gm7 = gm5 - cal["e00"]

cal["e30"] = cal["isolation"].z
cal["e10e01"] = cal["e00"] * cal["e11"] - cal["delta_e"]
cal["e22"] = gm7 / (
gm7 * cal["e11"] * gt**2 + cal["e10e01"] * gt**2)
cal["e10e32"] = (gm4 - gm6) * (
1 - cal["e11"] * cal["e22"] *gt**2) / gt

def calc_corrections(self):
if not self.isValid1Port():
logger.warning(
Expand All @@ -190,27 +232,10 @@ def calc_corrections(self):
logger.debug("Calculating calibration for %d points.", self.size())

for freq, caldata in self.dataset.items():
g1 = self.gamma_short(freq)
g2 = self.gamma_open(freq)
g3 = self.gamma_load(freq)

gm1 = caldata["short"].z
gm2 = caldata["open"].z
gm3 = caldata["load"].z

try:
denominator = (g1 * (g2 - g3) * gm1 +
g2 * g3 * gm2 - g2 * g3 * gm3 -
(g2 * gm2 - g3 * gm3) * g1)
caldata["e00"] = - ((g2 * gm3 - g3 * gm3) * g1 * gm2 -
(g2 * g3 * gm2 - g2 * g3 * gm3 -
(g3 * gm2 - g2 * gm3) * g1) * gm1
) / denominator
caldata["e11"] = ((g2 - g3) * gm1 - g1 * (gm2 - gm3) +
g3 * gm2 - g2 * gm3) / denominator
caldata["delta_e"] = - ((g1 * (gm2 - gm3) - g2 * gm2 + g3 *
gm3) * gm1 + (g2 * gm3 - g3 * gm3) *
gm2) / denominator
self._calc_port_1(freq, caldata)
if self.isValid2Port():
self._calc_port_2(freq, caldata)
except ZeroDivisionError as exc:
self.isCalculated = False
logger.error(
Expand All @@ -220,13 +245,6 @@ def calc_corrections(self):
f"Two of short, open and load returned the same"
f" values at frequency {freq}Hz.") from exc

if self.isValid2Port():
caldata["e30"] = caldata["isolation"].z

gt = self.gamma_through(freq)
caldata["e10e32"] = (caldata["through"].z / gt - caldata["e30"]
) * (1 - caldata["e11"]**2)

self.gen_interpolation()
self.isCalculated = True
logger.debug("Calibration correctly calculated.")
Expand Down Expand Up @@ -280,15 +298,19 @@ def gen_interpolation(self):
e00 = []
e11 = []
delta_e = []
e10e01 = []
e30 = []
e22 = []
e10e32 = []

for caldata in self.dataset.values():
freq.append(caldata["freq"])
e00.append(caldata["e00"])
e11.append(caldata["e11"])
delta_e.append(caldata["delta_e"])
e10e01.append(caldata["e10e01"])
e30.append(caldata["e30"])
e22.append(caldata["e22"])
e10e32.append(caldata["e10e32"])

self.interp = {
Expand All @@ -301,9 +323,15 @@ def gen_interpolation(self):
"delta_e": interp1d(freq, delta_e,
kind="slinear", bounds_error=False,
fill_value=(delta_e[0], delta_e[-1])),
"e10e01": interp1d(freq, e10e01,
kind="slinear", bounds_error=False,
fill_value=(e10e01[0], e10e01[-1])),
"e30": interp1d(freq, e30,
kind="slinear", bounds_error=False,
fill_value=(e30[0], e30[-1])),
"e22": interp1d(freq, e22,
kind="slinear", bounds_error=False,
fill_value=(e22[0], e22[-1])),
"e10e32": interp1d(freq, e10e32,
kind="slinear", bounds_error=False,
fill_value=(e10e32[0], e10e32[-1])),
Expand All @@ -315,23 +343,24 @@ def correct11(self, dp: Datapoint):
(dp.z * i["e11"](dp.freq)) - i["delta_e"](dp.freq))
return Datapoint(dp.freq, s11.real, s11.imag)

def correct21(self, dp: Datapoint):
def correct21(self, dp: Datapoint, dp11: Datapoint):
i = self.interp
s21 = (dp.z - i["e30"](dp.freq)) / i["e10e32"](dp.freq)
s21 = s21 * (i["e10e01"](dp.freq)/(i["e11"](dp.freq)*dp11.z-i["delta_e"](dp.freq)))
return Datapoint(dp.freq, s21.real, s21.imag)

# TODO: implement tests
def save(self, filename: str):
# Save the calibration data to file
if not self.isValid1Port():
raise ValueError("Not a valid 1-Port calibration")
with open(f"{filename}", "w") as calfile:
with open(filename, mode="w", encoding='utf-8') as calfile:
calfile.write("# Calibration data for NanoVNA-Saver\n")
for note in self.notes:
calfile.write(f"! {note}\n")
calfile.write(
"# Hz ShortR ShortI OpenR OpenI LoadR LoadI"
" ThroughR ThroughI IsolationR IsolationI\n")
" ThroughR ThroughI ThrureflR ThrureflI IsolationR IsolationI\n")
for freq in self.dataset.frequencies():
calfile.write(f"{self.dataset.get(freq)}\n")

Expand All @@ -343,7 +372,7 @@ def load(self, filename):
self.notes = []

parsed_header = False
with open(filename) as calfile:
with open(filename, encoding='utf-8') as calfile:
for i, line in enumerate(calfile):
line = line.strip()
if line.startswith("!"):
Expand All @@ -353,7 +382,7 @@ def load(self, filename):
if line.startswith("#"):
if not parsed_header and line == (
"# Hz ShortR ShortI OpenR OpenI LoadR LoadI"
" ThroughR ThroughI IsolationR IsolationI"):
" ThroughR ThroughI ThrureflR ThrureflI IsolationR IsolationI"):
parsed_header = True
continue
if not parsed_header:
Expand All @@ -367,7 +396,7 @@ def load(self, filename):
logger.warning("Illegal data in cal file. Line %i", i)
cal = m.groupdict()

nr_cals = 5 if cal["throughr"] else 3
nr_cals = 6 if cal["throughr"] else 3
for name in Calibration.CAL_NAMES[:nr_cals]:
self.dataset.insert(
name,
Expand Down
12 changes: 9 additions & 3 deletions NanoVNASaver/Charts/VSWR.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -73,7 +73,10 @@ def drawValues(self, qp: QtGui.QPainter):
vswr = d.vswr
if vswr > maxVSWR:
maxVSWR = vswr
maxVSWR = min(self.maxDisplayValue, math.ceil(maxVSWR))
try:
maxVSWR = min(self.maxDisplayValue, math.ceil(maxVSWR))
except OverflowError:
maxVSWR = self.maxDisplayValue
self.maxVSWR = maxVSWR
span = maxVSWR-minVSWR
if span == 0:
Expand Down Expand Up @@ -152,7 +155,10 @@ def getYPositionFromValue(self, vswr) -> int:
return (
self.topMargin +
round((math.log(self.maxVSWR) - math.log(vswr)) / span * self.dim.height))
return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.dim.height)
try:
return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.dim.height)
except OverflowError:
return self.topMargin

def getYPosition(self, d: Datapoint) -> int:
return self.getYPositionFromValue(d.vswr)
Expand Down
8 changes: 2 additions & 6 deletions NanoVNASaver/Formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,13 @@ def format_complex_adm(z: complex, allow_negative: bool = False) -> str:
return "- S"
adm = 1/z

fmt_re = FMT_COMPLEX
if allow_negative:
fmt_re = FMT_COMPLEX_NEG
fmt_re = FMT_COMPLEX_NEG if allow_negative else FMT_COMPLEX
re = SITools.Value(adm.real, fmt=fmt_re)
im = SITools.Value(abs(adm.imag), fmt=FMT_COMPLEX)
return f"{re}{'-' if adm.imag < 0 else '+'}j{im} S"

def format_complex_imp(z: complex, allow_negative: bool = False) -> str:
fmt_re = FMT_COMPLEX
if allow_negative:
fmt_re = FMT_COMPLEX_NEG
fmt_re = FMT_COMPLEX_NEG if allow_negative else FMT_COMPLEX
re = SITools.Value(z.real, fmt=fmt_re)
im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX)
return f"{re}{'-' if z.imag < 0 else '+'}j{im} ""\N{OHM SIGN}"
Expand Down
Loading

0 comments on commit 582b442

Please sign in to comment.