First versions of permeability- and group delay-charts.

pull/89/head
Rune Broberg 2019-11-08 13:58:14 +01:00
rodzic 66859c81a7
commit 72c95bc8ba
2 zmienionych plików z 446 dodań i 2 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ import math
from typing import List, Set from typing import List, Set
import numpy as np import numpy as np
import logging import logging
from scipy import signal
from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
@ -2382,7 +2383,8 @@ class RealImaginaryChart(FrequencyChart):
# #
self.setMinimumSize(self.chartWidth + self.leftMargin + self.rightMargin, self.chartHeight + 40) self.setMinimumSize(self.chartWidth + self.leftMargin + self.rightMargin, self.chartHeight + 40)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette() pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor) pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal) self.setPalette(pal)
@ -3051,3 +3053,438 @@ class MagnitudeZChart(FrequencyChart):
new_chart: LogMagChart = super().copy() new_chart: LogMagChart = super().copy()
new_chart.span = self.span new_chart.span = self.span
return new_chart return new_chart
class PermeabilityChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 40
self.rightMargin = 30
self.chartWidth = 230
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.span = 0.01
self.max = 0
self.maxDisplayValue = 100
self.minDisplayValue = -100
#
# Set up size policy and palette
#
self.setMinimumSize(self.chartWidth + self.leftMargin + self.rightMargin, self.chartHeight + 40)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def copy(self):
new_chart: PermabilityChart = super().copy()
return new_chart
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
if len(self.data) > 0:
fstart = self.data[0].freq
fstop = self.data[len(self.data)-1].freq
else:
fstart = self.reference[0].freq
fstop = self.reference[len(self.reference) - 1].freq
self.fstart = fstart
self.fstop = fstop
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
# Find scaling
if self.fixedValues:
min = self.minDisplayValue
max = self.maxDisplayValue
else:
min = 1000
max = -1000
for d in self.data:
re, im = RFTools.normalize50(d)
re = re * 10e6 / d.freq
im = im * 10e6 / d.freq
if re > max:
max = re
if re < min:
min = re
if im > max:
max = im
if im < min:
min = im
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < fstart or d.freq > fstop:
continue
re, im = RFTools.normalize50(d)
re = re * 10e6 / d.freq
im = im * 10e6 / d.freq
if re > max:
max = re
if re < min:
min = re
if im > max:
max = im
if im < min:
min = im
self.max = max
span = max - min
if span == 0:
span = 0.01
self.span = span
# We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.chartHeight/50)
for i in range(horizontal_ticks):
y = self.topMargin + round(i * self.chartHeight / horizontal_ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y)
qp.setPen(QtGui.QPen(self.textColor))
val = max - i * span / horizontal_ticks
qp.drawText(3, y + 4, str(round(val, 1)))
qp.drawText(3, self.chartHeight + self.topMargin, str(round(min, 1)))
self.drawFrequencyTicks(qp)
primary_pen = pen
secondary_pen = QtGui.QPen(self.secondarySweepColor)
if len(self.data) > 0:
c = QtGui.QColor(self.sweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 9, 25, 9)
c = QtGui.QColor(self.secondarySweepColor)
c.setAlpha(255)
pen.setColor(c)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9)
primary_pen.setWidth(self.pointSize)
secondary_pen.setWidth(self.pointSize)
line_pen.setWidth(self.lineThickness)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y_re = self.getReYPosition(self.data[i])
y_im = self.getImYPosition(self.data[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.data[i - 1])
prev_y_re = self.getReYPosition(self.data[i-1])
prev_y_im = self.getImYPosition(self.data[i-1])
# Real part first
line_pen.setColor(self.sweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
# Imag part second
line_pen.setColor(self.secondarySweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
primary_pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
secondary_pen.setColor(self.secondaryReferenceColor)
qp.setPen(primary_pen)
if len(self.reference) > 0:
c = QtGui.QColor(self.referenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 14, 25, 14)
c = QtGui.QColor(self.secondaryReferenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 14, self.leftMargin + self.chartWidth + 5, 14)
for i in range(len(self.reference)):
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
continue
x = self.getXPosition(self.reference[i])
y_re = self.getReYPosition(self.reference[i])
y_im = self.getImYPosition(self.reference[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.reference[i - 1])
prev_y_re = self.getReYPosition(self.reference[i-1])
prev_y_im = self.getImYPosition(self.reference[i-1])
line_pen.setColor(self.referenceColor)
qp.setPen(line_pen)
# Real part first
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
line_pen.setColor(self.secondaryReferenceColor)
qp.setPen(line_pen)
# Imag part second
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
# Now draw the markers
for m in self.markers:
if m.location != -1:
x = self.getXPosition(self.data[m.location])
y_re = self.getReYPosition(self.data[m.location])
y_im = self.getImYPosition(self.data[m.location])
self.drawMarker(x, y_re, qp, m.color, self.markers.index(m)+1)
self.drawMarker(x, y_im, qp, m.color, self.markers.index(m)+1)
def getImYPosition(self, d: Datapoint) -> int:
# TODO: Logarithmic Y positions
_, im = RFTools.normalize50(d)
im = im * 10e6 / d.freq
return self.topMargin + round((self.max - im) / self.span * self.chartHeight)
def getReYPosition(self, d: Datapoint) -> int:
re, _ = RFTools.normalize50(d)
re = re * 10e6 / d.freq
return self.topMargin + round((self.max - re) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.max)
return [val]
def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0:
return None
shortest = 10**6
nearest = None
for m in self.markers:
mx, _ = self.getPosition(self.data[m.location])
myr = self.getReYPosition(self.data[m.location])
myi = self.getImYPosition(self.data[m.location])
dx = abs(x - mx)
dy = min(abs(y - myr), abs(y-myi))
distance = math.sqrt(dx**2 + dy**2)
if distance < shortest:
shortest = distance
nearest = m
return nearest
class GroupDelayChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 40
self.chartWidth = 250
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.minDelay = 0
self.maxDelay = 0
self.span = 0
self.unwrappedData = []
self.unwrappedReference = []
self.groupDelay = []
self.groupDelayReference = []
self.minDisplayValue = -180
self.maxDisplayValue = 180
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin,
self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def copy(self):
new_chart: GroupDelayChart = super().copy()
return new_chart
def setData(self, data):
self.data = data
rawData = []
for d in self.data:
rawData.append(RFTools.phaseAngle(d))
rawReference = []
for d in self.reference:
rawReference.append(RFTools.phaseAngle(d))
if len(self.data) > 0:
self.unwrappedData = np.unwrap(rawData, 180)
self.groupDelay = signal.convolve(self.unwrappedData, [1, -1], mode='same')
# TODO: Modify to show values as ps instead of $undefined?
if len(self.reference) > 0:
self.unwrappedReference = np.unwrap(rawReference, 180)
self.groupDelayReference = signal.convolve(self.unwrappedReference, [1, -1], mode='same')
self.update()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
if self.fixedValues:
min_delay = self.minDisplayValue
max_delay = self.maxDisplayValue
elif self.data:
min_delay = math.floor(np.min(self.groupDelay))
max_delay = math.ceil(np.max(self.groupDelay))
elif self.reference:
min_delay = math.floor(np.min(self.groupDelayReference))
max_delay = math.ceil(np.max(self.groupDelayReference))
span = max_delay - min_delay
if span == 0:
span = 0.01
self.minDelay = min_delay
self.maxDelay = max_delay
self.span = span
tickcount = math.floor(self.chartHeight / 60)
for i in range(tickcount):
delay = min_delay + span * i / tickcount
y = self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight)
if delay != min_delay and delay != max_delay:
qp.setPen(QtGui.QPen(self.textColor))
if delay != 0:
digits = max(0, min(2, math.floor(3 - math.log10(abs(delay)))))
if digits == 0:
delaystr = str(round(delay))
else:
delaystr = str(round(delay, digits))
else:
delaystr = "0"
qp.drawText(3, y + 3, delaystr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 5, str(max_delay))
qp.drawText(3, self.chartHeight + self.topMargin, str(min_delay))
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
if len(self.data) > 0:
fstart = self.data[0].freq
fstop = self.data[len(self.data)-1].freq
else:
fstart = self.reference[0].freq
fstop = self.reference[len(self.reference) - 1].freq
self.fstart = fstart
self.fstop = fstop
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
# TODO: Find a faster way than these expensive "d in self.data" lookups
if d in self.data:
delay = self.groupDelay[self.data.index(d)]
elif d in self.reference:
delay = self.groupDelayReference[self.reference.index(d)]
else:
delay = 0
return self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxDelay)
return [val]

Wyświetl plik

@ -31,7 +31,8 @@ from serial.tools import list_ports
from .Hardware import VNA, InvalidVNA, Version from .Hardware import VNA, InvalidVNA, Version
from .RFTools import RFTools, Datapoint from .RFTools import RFTools, Datapoint
from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \ from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \
RealImaginaryChart, MagnitudeChart, MagnitudeZChart, CombinedLogMagChart, SParameterChart RealImaginaryChart, MagnitudeChart, MagnitudeZChart, CombinedLogMagChart, SParameterChart, PermeabilityChart, \
GroupDelayChart
from .Calibration import CalibrationWindow, Calibration from .Calibration import CalibrationWindow, Calibration
from .Marker import Marker from .Marker import Marker
from .SweepWorker import SweepWorker from .SweepWorker import SweepWorker
@ -132,6 +133,9 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s11MagZ = MagnitudeZChart("S11 |Z|") self.s11MagZ = MagnitudeZChart("S11 |Z|")
self.s11Phase = PhaseChart("S11 Phase") self.s11Phase = PhaseChart("S11 Phase")
self.s21Phase = PhaseChart("S21 Phase") self.s21Phase = PhaseChart("S21 Phase")
self.s11GroupDelay = GroupDelayChart("S11 Group Delay")
self.s21GroupDelay = GroupDelayChart("S21 Group Delay")
self.permabilityChart = PermeabilityChart("S11 R/\N{GREEK SMALL LETTER OMEGA} & X/\N{GREEK SMALL LETTER OMEGA}")
self.s11VSWR = VSWRChart("S11 VSWR") self.s11VSWR = VSWRChart("S11 VSWR")
self.s11QualityFactor = QualityFactorChart("S11 Quality Factor") self.s11QualityFactor = QualityFactorChart("S11 Quality Factor")
self.s11RealImaginary = RealImaginaryChart("S11 R+jX") self.s11RealImaginary = RealImaginaryChart("S11 R+jX")
@ -146,10 +150,12 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s11charts.append(self.s11Mag) self.s11charts.append(self.s11Mag)
self.s11charts.append(self.s11MagZ) self.s11charts.append(self.s11MagZ)
self.s11charts.append(self.s11Phase) self.s11charts.append(self.s11Phase)
self.s11charts.append(self.s11GroupDelay)
self.s11charts.append(self.s11VSWR) self.s11charts.append(self.s11VSWR)
self.s11charts.append(self.s11RealImaginary) self.s11charts.append(self.s11RealImaginary)
self.s11charts.append(self.s11QualityFactor) self.s11charts.append(self.s11QualityFactor)
self.s11charts.append(self.s11SParameterChart) self.s11charts.append(self.s11SParameterChart)
self.s11charts.append(self.permabilityChart)
# List of all the S21 charts, for selecting # List of all the S21 charts, for selecting
self.s21charts: List[Chart] = [] self.s21charts: List[Chart] = []
@ -157,6 +163,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s21charts.append(self.s21LogMag) self.s21charts.append(self.s21LogMag)
self.s21charts.append(self.s21Mag) self.s21charts.append(self.s21Mag)
self.s21charts.append(self.s21Phase) self.s21charts.append(self.s21Phase)
self.s21charts.append(self.s21GroupDelay)
self.s21charts.append(self.s21SParameterChart) self.s21charts.append(self.s21SParameterChart)
# List of all charts that use both S11 and S21 # List of all charts that use both S11 and S21