Skip to content

Commit

Permalink
Merge pull request #5 from mihtjel/ReferenceSweeps
Browse files Browse the repository at this point in the history
Reference sweeps
  • Loading branch information
mihtjel authored Sep 2, 2019
2 parents 2a69c3f + 2b3ae91 commit 8bac22a
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 71 deletions.
54 changes: 54 additions & 0 deletions Chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import collections
from typing import List

from PyQt5 import QtWidgets, QtGui, QtCore

from Marker import Marker
Datapoint = collections.namedtuple('Datapoint', 'freq re im')


class Chart(QtWidgets.QWidget):
sweepColor = QtCore.Qt.darkYellow
referenceColor : QtGui.QColor = QtGui.QColor(QtCore.Qt.blue)
referenceColor.setAlpha(64)
data : List[Datapoint] = []
reference : List[Datapoint] = []
markers: List[Marker] = []

def setSweepColor(self, color : QtGui.QColor):
self.sweepColor = color
self.update()

def setReferenceColor(self, color : QtGui.QColor):
self.referenceColor = color
self.update()

def setReference(self, data):
self.reference = data
self.update()

def resetReference(self):
self.reference = []
self.update()

def setData(self, data):
self.data = data
self.update()

def setMarkers(self, markers):
self.markers = markers
73 changes: 43 additions & 30 deletions LogMagChart.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@

from PyQt5 import QtWidgets, QtGui, QtCore

from Chart import Chart
from Marker import Marker

Datapoint = collections.namedtuple('Datapoint', 'freq re im')

class LogMagChart(QtWidgets.QWidget):

class LogMagChart(Chart):
def __init__(self, name=""):
super().__init__()
self.leftMargin = 30
Expand All @@ -51,15 +53,8 @@ def __init__(self, name=""):
self.setPalette(pal)
self.setAutoFillBackground(True)

self.values = []
self.frequencies = []
self.data : List[Datapoint] = []
self.markers : List[Marker] = []

self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
self.sweepColor = QtGui.QColor(220, 200, 30, 128)


def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-20-self.leftMargin
Expand All @@ -80,12 +75,19 @@ def drawChart(self, qp: QtGui.QPainter):
qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth, 20 + self.chartHeight)

def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0:
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(2)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(3)
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
fspan = fstop-fstart
# Find scaling
min = 100
max = 0
Expand All @@ -101,6 +103,21 @@ def drawValues(self, qp: QtGui.QPainter):
max = logmag
if logmag < min:
min = logmag
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < fstart or d.freq > fstop:
continue
re = d.re
im = d.im
re50 = 50 * (1 - re * re - im * im) / (1 + re * re + im * im - 2 * re)
im50 = 50 * (2 * im) / (1 + re * re + im * im - 2 * re)
# Calculate the reflection coefficient
mag = math.sqrt((re50-50)*(re50-50) + im50 * im50)/math.sqrt((re50+50)*(re50+50) + im50 * im50)
logmag = -20 * math.log10(mag)
if logmag > max:
max = logmag
if logmag < min:
min = logmag

min = 10*math.floor(min/10)
max = 10*math.ceil(max/10)
span = max-min
Expand All @@ -110,10 +127,6 @@ def drawValues(self, qp: QtGui.QPainter):
qp.setPen(QtCore.Qt.black)
qp.drawText(3, 35, str(-min))
qp.drawText(3, self.chartHeight+20, str(-max))
# Draw frequency markers
fstart = self.data[0].freq
fstop = self.data[len(self.data)-1].freq
fspan = fstop-fstart
# At least 100 px between ticks
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, LogMagChart.shortenFrequency(fstart))
ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin
Expand All @@ -136,6 +149,22 @@ def drawValues(self, qp: QtGui.QPainter):
x = self.leftMargin + 1 + round(self.chartWidth/len(self.data) * i)
y = 30 + round((logmag-min)/span*(self.chartHeight-10))
qp.drawPoint(int(x), int(y))
pen.setColor(self.referenceColor)
qp.setPen(pen)
for i in range(len(self.reference)):
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
continue

re = self.reference[i].re
im = self.reference[i].im
re50 = 50 * (1 - re * re - im * im) / (1 + re * re + im * im - 2 * re)
im50 = 50 * (2 * im) / (1 + re * re + im * im - 2 * re)
# Calculate the reflection coefficient
mag = math.sqrt((re50-50)*(re50-50) + im50 * im50)/math.sqrt((re50+50)*(re50+50) + im50 * im50)
logmag = -20 * math.log10(mag)
x = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i].freq - fstart)/fspan)
y = 30 + round((logmag-min)/span*(self.chartHeight-10))
qp.drawPoint(int(x), int(y))
# Now draw the markers
for m in self.markers:
if m.location != -1:
Expand All @@ -153,26 +182,10 @@ def drawValues(self, qp: QtGui.QPainter):
y = 30 + round((logmag - min) / span * (self.chartHeight - 10))
qp.drawPoint(int(x), int(y))

def setValues(self, values, frequencies):
self.values = values
self.frequencies = frequencies
self.update()

def setData(self, data):
self.data = data
self.update()

def setMarkers(self, markers):
self.markers = markers

@staticmethod
def shortenFrequency(frequency):
if frequency < 50000:
return frequency
if frequency < 5000000:
return str(round(frequency / 1000)) + "k"
return str(round(frequency / 1000000, 1)) + "M"

def setSweepColor(self, color : QtGui.QColor):
self.sweepColor = color
self.update()
return str(round(frequency / 1000000, 1)) + "M"
113 changes: 101 additions & 12 deletions NanoVNASaver.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import serial
from PyQt5 import QtWidgets, QtCore, QtGui

import Chart
from Marker import Marker
from SmithChart import SmithChart
from SweepWorker import SweepWorker
from LogMagChart import LogMagChart
from Touchstone import Touchstone

Datapoint = collections.namedtuple('Datapoint', 'freq re im')

Expand All @@ -45,26 +47,34 @@ def __init__(self):
self.serial = serial.Serial()

self.dataLock = threading.Lock()
self.values = []
self.frequencies = []
self.data : List[Datapoint] = []
self.data21 : List[Datapoint] = []
self.referenceS11data : List[Datapoint] = []
self.referenceS21data : List[Datapoint] = []

self.markers = []

self.serialPort = "COM11"
# self.serialSpeed = "115200"

self.color = QtGui.QColor(160, 140, 20, 128)
self.referenceColor = QtGui.QColor(0, 0, 255, 32)

self.setWindowTitle("NanoVNA Saver")
layout = QtWidgets.QGridLayout()
self.setLayout(layout)

self.smithChart = SmithChart("S11")
self.s11SmithChart = SmithChart("S11")
self.s21SmithChart = SmithChart("S21")
self.s11LogMag = LogMagChart("S11 Return Loss")
self.s21LogMag = LogMagChart("S21 Gain")

self.charts : List[Chart] = []
self.charts.append(self.s11SmithChart)
self.charts.append(self.s21SmithChart)
self.charts.append(self.s11LogMag)
self.charts.append(self.s21LogMag)

left_column = QtWidgets.QVBoxLayout()
right_column = QtWidgets.QVBoxLayout()

Expand Down Expand Up @@ -135,7 +145,7 @@ def __init__(self):
marker_control_layout.addRow(label, layout)
self.markers.append(marker2)

self.smithChart.setMarkers(self.markers)
self.s11SmithChart.setMarkers(self.markers)
self.s21SmithChart.setMarkers(self.markers)

self.marker1label = QtWidgets.QLabel("")
Expand Down Expand Up @@ -219,6 +229,48 @@ def __init__(self):

left_column.addSpacerItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))

################################################################################################################
# Reference control
################################################################################################################

reference_control_box = QtWidgets.QGroupBox()
reference_control_box.setMaximumWidth(400)
reference_control_box.setTitle("Reference sweep")
reference_control_layout = QtWidgets.QFormLayout(reference_control_box)

btnSetReference = QtWidgets.QPushButton("Set current as reference")
btnSetReference.clicked.connect(self.setReference)
self.btnResetReference = QtWidgets.QPushButton("Reset reference")
self.btnResetReference.clicked.connect(self.resetReference)
self.btnResetReference.setDisabled(True)
self.btnReferenceColorPicker = QtWidgets.QPushButton("█")
self.btnReferenceColorPicker.setFixedWidth(20)
self.setReferenceColor(self.referenceColor)
self.btnReferenceColorPicker.clicked.connect(lambda: self.setReferenceColor(
QtWidgets.QColorDialog.getColor(self.referenceColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))

set_reference_layout = QtWidgets.QHBoxLayout()
set_reference_layout.addWidget(btnSetReference)
set_reference_layout.addWidget(self.btnReferenceColorPicker)
reference_control_layout.addRow(set_reference_layout)
reference_control_layout.addRow(self.btnResetReference)

self.referenceFileNameInput = QtWidgets.QLineEdit("")
btnReferenceFilePicker = QtWidgets.QPushButton("...")
btnReferenceFilePicker.setMaximumWidth(25)
btnReferenceFilePicker.clicked.connect(self.pickReferenceFile)
referenceFileNameLayout = QtWidgets.QHBoxLayout()
referenceFileNameLayout.addWidget(self.referenceFileNameInput)
referenceFileNameLayout.addWidget(btnReferenceFilePicker)

reference_control_layout.addRow(QtWidgets.QLabel("Filename"), referenceFileNameLayout)

btnImportReference = QtWidgets.QPushButton("Import reference file")
btnImportReference.clicked.connect(self.loadReferenceFile)
reference_control_layout.addRow(btnImportReference)

left_column.addWidget(reference_control_box)

################################################################################################################
# Serial control
################################################################################################################
Expand Down Expand Up @@ -250,7 +302,6 @@ def __init__(self):
file_control_box.setMaximumWidth(400)
file_control_layout = QtWidgets.QFormLayout(file_control_box)
self.fileNameInput = QtWidgets.QLineEdit("")
self.fileNameInput.setAlignment(QtCore.Qt.AlignRight)
btnFilePicker = QtWidgets.QPushButton("...")
btnFilePicker.setMaximumWidth(25)
btnFilePicker.clicked.connect(self.pickFile)
Expand All @@ -277,7 +328,7 @@ def __init__(self):
self.lister = QtWidgets.QPlainTextEdit()
self.lister.setFixedHeight(100)
charts = QtWidgets.QGridLayout()
charts.addWidget(self.smithChart, 0, 0)
charts.addWidget(self.s11SmithChart, 0, 0)
charts.addWidget(self.s21SmithChart, 1, 0)
charts.addWidget(self.s11LogMag, 0, 1)
charts.addWidget(self.s21LogMag, 1, 1)
Expand All @@ -291,6 +342,10 @@ def __init__(self):
self.worker.signals.updated.connect(self.dataUpdated)
self.worker.signals.finished.connect(self.sweepFinished)

def pickReferenceFile(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(directory=self.referenceFileNameInput.text(), filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
self.referenceFileNameInput.setText(filename)

def pickFile(self):
filename, _ = QtWidgets.QFileDialog.getSaveFileName(directory=self.fileNameInput.text(), filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
self.fileNameInput.setText(filename)
Expand Down Expand Up @@ -473,7 +528,7 @@ def dataUpdated(self):
im50str = "+ j" + str(round(im50, 3))
self.marker2label.setText(str(round(re50, 3)) + im50str + " VSWR: 1:" + str(round(vswr, 3)))

self.smithChart.setData(self.data)
self.s11SmithChart.setData(self.data)
self.s21SmithChart.setData(self.data21)
self.s11LogMag.setData(self.data)
self.s21LogMag.setData(self.data21)
Expand Down Expand Up @@ -586,10 +641,8 @@ def setSweepColor(self, color : QtGui.QColor):
p.setColor(QtGui.QPalette.ButtonText, color)
self.btnColorPicker.setPalette(p)

self.smithChart.setSweepColor(color)
self.s21SmithChart.setSweepColor(color)
self.s11LogMag.setSweepColor(color)
self.s21LogMag.setSweepColor(color)
for c in self.charts:
c.setSweepColor(color)

@staticmethod
def formatFrequency(freq):
Expand Down Expand Up @@ -626,4 +679,40 @@ def parseFrequency(freq : str):
return int(round(multiplier * f))
except ValueError:
# Okay, we couldn't parse this however much we tried.
return -1
return -1

def setReference(self):
self.setReference(self.data, self.data21)

def setReference(self, s11data, s21data):
self.referenceS11data = s11data
self.s11SmithChart.setReference(s11data)
self.s11LogMag.setReference(s11data)

self.referenceS21data = s21data
self.s21SmithChart.setReference(s21data)
self.s21LogMag.setReference(s21data)
self.btnResetReference.setDisabled(False)

def resetReference(self):
self.referenceS11data = []
self.referenceS21data = []
for c in self.charts:
c.resetReference()
self.btnResetReference.setDisabled(True)

def setReferenceColor(self, color):
if color.isValid():
self.referenceColor = color
p = self.btnReferenceColorPicker.palette()
p.setColor(QtGui.QPalette.ButtonText, color)
self.btnReferenceColorPicker.setPalette(p)

for c in self.charts:
c.setReferenceColor(color)

def loadReferenceFile(self):
filename = self.referenceFileNameInput.text()
t = Touchstone(filename)
t.load()
self.setReference(t.s11data, t.s21data)
Loading

0 comments on commit 8bac22a

Please sign in to comment.