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
import numpy as np
import logging
from scipy import signal
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal
@ -2382,7 +2383,8 @@ class RealImaginaryChart(FrequencyChart):
#
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.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
@ -3051,3 +3053,438 @@ class MagnitudeZChart(FrequencyChart):
new_chart: LogMagChart = super().copy()
new_chart.span = self.span
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 .RFTools import RFTools, Datapoint
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 .Marker import Marker
from .SweepWorker import SweepWorker
@ -132,6 +133,9 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s11MagZ = MagnitudeZChart("S11 |Z|")
self.s11Phase = PhaseChart("S11 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.s11QualityFactor = QualityFactorChart("S11 Quality Factor")
self.s11RealImaginary = RealImaginaryChart("S11 R+jX")
@ -146,10 +150,12 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s11charts.append(self.s11Mag)
self.s11charts.append(self.s11MagZ)
self.s11charts.append(self.s11Phase)
self.s11charts.append(self.s11GroupDelay)
self.s11charts.append(self.s11VSWR)
self.s11charts.append(self.s11RealImaginary)
self.s11charts.append(self.s11QualityFactor)
self.s11charts.append(self.s11SParameterChart)
self.s11charts.append(self.permabilityChart)
# List of all the S21 charts, for selecting
self.s21charts: List[Chart] = []
@ -157,6 +163,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s21charts.append(self.s21LogMag)
self.s21charts.append(self.s21Mag)
self.s21charts.append(self.s21Phase)
self.s21charts.append(self.s21GroupDelay)
self.s21charts.append(self.s21SParameterChart)
# List of all charts that use both S11 and S21