moved groupDelay out of  unneeded RFTools class
splited some Marker code to functions
pull/108/head
Holger Mueller 2019-11-23 11:45:34 +01:00
rodzic d1696c594f
commit 0d99293e98
2 zmienionych plików z 139 dodań i 118 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
# NanoVNASaver
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
@ -20,7 +21,15 @@ from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal
from NanoVNASaver import SITools
from NanoVNASaver.RFTools import Datapoint, RFTools
from NanoVNASaver.RFTools import Datapoint, RFTools, groupDelay
FMT_Q_FACTOR = SITools.Format(max_nr_digits=4, assume_infinity=False,
min_offset=0, max_offset=0, allow_strip=True)
def format_q_factor(val: float) -> str:
if 0 > val or val > 10000.0:
return "\N{INFINITY}"
return str(SITools.Value(val, fmt=FMT_Q_FACTOR))
class Marker(QtCore.QObject):
@ -73,7 +82,6 @@ class Marker(QtCore.QObject):
self.frequency_label.setMinimumWidth(100)
self.impedance_label = QtWidgets.QLabel("")
self.admittance_label = QtWidgets.QLabel("")
# self.admittance_label = QtWidgets.QLabel("")
self.parallel_r_label = QtWidgets.QLabel("")
self.parallel_x_label = QtWidgets.QLabel("")
self.parallel_c_label = QtWidgets.QLabel("")
@ -231,8 +239,11 @@ class Marker(QtCore.QObject):
if self.coloredText:
color_string = QtCore.QVariant(color)
color_string.convert(QtCore.QVariant.String)
self.group_box.setStyleSheet('QGroupBox { color: ' + color_string.value() + '; font-size: ' + \
str(self.group_box.font().pointSize()) + '};')
self.group_box.setStyleSheet(
'QGroupBox { color: ' + color_string.value() +
'; font-size: ' + str(self.group_box.font().pointSize()) +
'};'
)
else:
self.group_box.setStyleSheet('QGroupBox { font-size: ' + str(self.group_box.font().pointSize()) + '};')
@ -304,103 +315,96 @@ class Marker(QtCore.QObject):
self.quality_factor_label.setText("")
def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]):
if self.location != -1:
imp = s11data[self.location].impedance()
re50, im50 = imp.real, imp.imag
vswr = s11data[self.location].vswr
if re50 > 0:
rp = (re50 ** 2 + im50 ** 2) / re50
rp = round(rp, 3 - max(0, math.floor(math.log10(abs(rp)))))
if rp > 10000:
rpstr = str(round(rp/1000, 2)) + "k"
elif rp > 1000:
rpstr = str(round(rp))
else:
rpstr = str(rp)
re50 = round(re50, 3 - max(0, math.floor(math.log10(abs(re50)))))
if re50 > 10000:
re50str = str(round(re50/1000, 2)) + "k"
elif re50 > 1000:
re50str = str(round(re50)) # Remove the ".0"
else:
re50str = str(re50)
if self.location == -1:
return
s11 = s11data[self.location]
if s21data:
s21 = s21data[self.location]
imp = s11.impedance()
re50, im50 = imp.real, imp.imag
vswr = s11.vswr
if re50 > 0:
rp = (re50 ** 2 + im50 ** 2) / re50
rp = round(rp, 3 - max(0, math.floor(math.log10(abs(rp)))))
if rp > 10000:
rpstr = str(round(rp/1000, 2)) + "k"
elif rp > 1000:
rpstr = str(round(rp))
else:
rpstr = "-"
re50 = 0
re50str = "-"
rpstr = str(rp)
if im50 != 0:
xp = (re50 ** 2 + im50 ** 2) / im50
xp = round(xp, 3 - max(0, math.floor(math.log10(abs(xp)))))
xpcstr = RFTools.capacitanceEquivalent(xp, s11data[self.location].freq)
xplstr = RFTools.inductanceEquivalent(xp, s11data[self.location].freq)
if xp < 0:
xpstr = xpcstr
xp50str = " -j" + str(-1 * xp)
else:
xpstr = xplstr
xp50str = " +j" + str(xp)
xp50str += " \N{OHM SIGN}"
re50 = round(re50, 3 - max(0, math.floor(math.log10(abs(re50)))))
if re50 > 10000:
re50str = str(round(re50/1000, 2)) + "k"
elif re50 > 1000:
re50str = str(round(re50)) # Remove the ".0"
else:
xp50str = " +j ? \N{OHM SIGN}"
xpstr = xpcstr = xplstr = "-"
re50str = str(re50)
else:
rpstr = "-"
re50 = 0
re50str = "-"
if im50 != 0:
im50 = round(im50, 3 - max(0, math.floor(math.log10(abs(im50)))))
if im50 < 0:
im50str = " -j" + str(-1 * im50)
if im50 != 0:
xp = (re50 ** 2 + im50 ** 2) / im50
xp = round(xp, 3 - max(0, math.floor(math.log10(abs(xp)))))
xpcstr = RFTools.capacitanceEquivalent(xp, s11data[self.location].freq)
xplstr = RFTools.inductanceEquivalent(xp, s11data[self.location].freq)
if xp < 0:
xpstr = xpcstr
xp50str = " -j" + str(-1 * xp)
else:
im50str = " +j" + str(im50)
im50str += " \N{OHM SIGN}"
xpstr = xplstr
xp50str = " +j" + str(xp)
xp50str += " \N{OHM SIGN}"
else:
xp50str = " +j ? \N{OHM SIGN}"
xpstr = xpcstr = xplstr = "-"
self.frequency_label.setText(RFTools.formatFrequency(s11data[self.location].freq))
self.impedance_label.setText(re50str + im50str)
self.admittance_label.setText(rpstr + xp50str)
self.series_r_label.setText(re50str + " \N{OHM SIGN}")
self.parallel_r_label.setText(rpstr + " \N{OHM SIGN}")
self.parallel_x_label.setText(xpstr)
if self.returnloss_is_positive:
returnloss = -round(s11data[self.location].gain, 3)
else:
returnloss = round(s11data[self.location].gain, 3)
self.returnloss_label.setText(str(returnloss) + " dB")
capacitance = RFTools.capacitanceEquivalent(im50, s11data[self.location].freq)
inductance = RFTools.inductanceEquivalent(im50, s11data[self.location].freq)
self.inductance_label.setText(inductance)
self.capacitance_label.setText(capacitance)
self.parallel_c_label.setText(xpcstr)
self.parallel_l_label.setText(xplstr)
if im50 > 0:
self.series_lc_label.setText(inductance)
else:
self.series_lc_label.setText(capacitance)
vswr = round(vswr, 3)
if vswr < 0:
vswr = "-"
self.vswr_label.setText(str(vswr))
q = s11data[self.location].qFactor()
if q > 10000 or q < 0:
q_str = "\N{INFINITY}"
elif q > 1000:
q_str = str(round(q, 0))
elif q > 100:
q_str = str(round(q, 1))
elif q > 10:
q_str = str(round(q, 2))
else:
q_str = str(round(q, 3))
self.quality_factor_label.setText(q_str)
self.s11_phase_label.setText(
str(round(math.degrees(s11data[self.location].phase), 2)) + "\N{DEGREE SIGN}")
fmt = SITools.Format(max_nr_digits=5, space_str=" ")
self.s11_group_delay_label.setText(str(SITools.Value(RFTools.groupDelay(s11data, self.location), "s", fmt)))
if im50 != 0:
im50 = round(im50, 3 - max(0, math.floor(math.log10(abs(im50)))))
if len(s21data) == len(s11data):
self.gain_label.setText(str(round(s21data[self.location].gain, 3)) + " dB")
self.s21_phase_label.setText(
str(round(math.degrees(s21data[self.location].phase), 2)) + "\N{DEGREE SIGN}")
self.s21_group_delay_label.setText(str(SITools.Value(RFTools.groupDelay(s21data, self.location) / 2,
"s", fmt)))
if im50 < 0:
im50str = " -j" + str(-1 * im50)
else:
im50str = " +j" + str(im50)
im50str += " \N{OHM SIGN}"
self.frequency_label.setText(RFTools.formatFrequency(s11data[self.location].freq))
self.impedance_label.setText(re50str + im50str)
self.admittance_label.setText(rpstr + xp50str)
self.series_r_label.setText(re50str + " \N{OHM SIGN}")
self.parallel_r_label.setText(rpstr + " \N{OHM SIGN}")
self.parallel_x_label.setText(xpstr)
if self.returnloss_is_positive:
returnloss = -round(s11data[self.location].gain, 3)
else:
returnloss = round(s11data[self.location].gain, 3)
self.returnloss_label.setText(str(returnloss) + " dB")
capacitance = RFTools.capacitanceEquivalent(im50, s11data[self.location].freq)
inductance = RFTools.inductanceEquivalent(im50, s11data[self.location].freq)
self.inductance_label.setText(inductance)
self.capacitance_label.setText(capacitance)
self.parallel_c_label.setText(xpcstr)
self.parallel_l_label.setText(xplstr)
if im50 > 0:
self.series_lc_label.setText(inductance)
else:
self.series_lc_label.setText(capacitance)
vswr = round(vswr, 3)
if vswr < 0:
vswr = "-"
self.vswr_label.setText(str(vswr))
q = s11data[self.location].qFactor()
self.quality_factor_label.setText(format_q_factor(q))
self.s11_phase_label.setText(
str(round(math.degrees(s11data[self.location].phase), 2)) + "\N{DEGREE SIGN}")
fmt = SITools.Format(max_nr_digits=5, space_str=" ")
self.s11_group_delay_label.setText(str(SITools.Value(groupDelay(s11data, self.location), "s", fmt)))
if len(s21data) == len(s11data):
self.gain_label.setText(str(round(s21data[self.location].gain, 3)) + " dB")
self.s21_phase_label.setText(
str(round(math.degrees(s21data[self.location].phase), 2)) + "\N{DEGREE SIGN}")
self.s21_group_delay_label.setText(str(SITools.Value(groupDelay(s21data, self.location) / 2,
"s", fmt)))

Wyświetl plik

@ -22,15 +22,23 @@ from typing import List, NamedTuple
from NanoVNASaver.SITools import Value, Format
def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real:
assert rmin <= rmax
if value < rmin:
return rmin
if value > rmax:
return rmax
return value
def normalize(z: complex, ref_impedance: float=50) -> complex:
"""Normalize from ref_impedance to z"""
return z / ref_impedance
def norm_to_impedance(z: complex, ref_impedance: float = 50) -> complex:
"""Calculate impedance from normalized z"""
return z * ref_impedance
def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex:
"""Calculate reflection coefficient for z"""
return (z - ref_impedance) / (z + ref_impedance)
def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex:
"""Calculate reflection coefficient for z"""
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
class Datapoint(NamedTuple):
freq: int
re: float
@ -63,7 +71,7 @@ class Datapoint(NamedTuple):
return (1 + mag) / (1 - mag)
def impedance(self, ref_impedance: float = 50) -> complex:
return ref_impedance * ((-self.z - 1) / (self.z - 1))
return gamma_to_impedance(self.z, ref_impedance)
def qFactor(self, ref_impedance: float = 50) -> float:
imp = self.impedance(ref_impedance)
@ -87,6 +95,28 @@ class Datapoint(NamedTuple):
return 0
return imp.imag * 1 / (self.freq * 2 * math.pi)
def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real:
assert rmin <= rmax
if value < rmin:
return rmin
if value > rmax:
return rmax
return value
def groupDelay(data: List[Datapoint], index: int) -> float:
idx0 = clamp_value(index - 1, 0, len(data) - 1)
idx1 = clamp_value(index + 1, 0, len(data) - 1)
delta_angle = (data[idx1].phase - data[idx0].phase)
if abs(delta_angle) > math.tau:
if delta_angle > 0:
delta_angle = delta_angle % math.tau
else:
delta_angle = -1 * (delta_angle % math.tau)
val = -delta_angle / math.tau / (data[idx1].freq - data[idx0].freq)
return val
class RFTools:
@staticmethod
@ -122,16 +152,3 @@ class RFTools:
return round(parser.parse(freq))
except (ValueError, IndexError):
return -1
@staticmethod
def groupDelay(data: List[Datapoint], index: int) -> float:
idx0 = clamp_value(index - 1, 0, len(data) - 1)
idx1 = clamp_value(index + 1, 0, len(data) - 1)
delta_angle = (data[idx1].phase - data[idx0].phase)
if abs(delta_angle) > math.tau:
if delta_angle > 0:
delta_angle = delta_angle % math.tau
else:
delta_angle = -1 * (delta_angle % math.tau)
val = -delta_angle / math.tau / (data[idx1].freq - data[idx0].freq)
return val