kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Feature/linting 220402 (#499)
* added .flatpak-builder to .gitingnore * GroupDelay Chart simplified * allow numpy > 1.21 fixes #456 * Added flatpak manifestpull/504/head
rodzic
140ce4906c
commit
6aa7aaa051
|
@ -11,4 +11,5 @@
|
|||
settings.json
|
||||
.gitignore
|
||||
.coverage
|
||||
/nanovna-saver.exe.spec
|
||||
.flatpak-builder
|
||||
/nanovna-saver.exe.spec
|
||||
|
|
|
@ -70,23 +70,21 @@ class MagLoopAnalysis(VSWRAnalysis):
|
|||
if len(self.minimums) == 1:
|
||||
m = self.minimums[0]
|
||||
start, lowest, end = m
|
||||
if start != end:
|
||||
if self.vswr_limit_value == self.vswr_bandwith_value:
|
||||
Q = self.app.data.s11[lowest].freq / \
|
||||
(self.app.data.s11[end].freq -
|
||||
self.app.data.s11[start].freq)
|
||||
self.layout.addRow(
|
||||
"Q", QtWidgets.QLabel("{}".format(int(Q))))
|
||||
new_start = self.app.data.s11[start].freq - self.bandwith
|
||||
new_end = self.app.data.s11[end].freq + self.bandwith
|
||||
logger.debug("Single Spot, new scan on %s-%s",
|
||||
new_start, new_end)
|
||||
|
||||
else:
|
||||
if start == end:
|
||||
new_start = self.app.data.s11[start].freq - 2 * self.bandwith
|
||||
new_end = self.app.data.s11[end].freq + 2 * self.bandwith
|
||||
logger.debug(" Zoom to %s-%s", new_start, new_end)
|
||||
|
||||
elif self.vswr_limit_value == self.vswr_bandwith_value:
|
||||
Q = self.app.data.s11[lowest].freq / \
|
||||
(self.app.data.s11[end].freq -
|
||||
self.app.data.s11[start].freq)
|
||||
self.layout.addRow("Q", QtWidgets.QLabel(f"{int(Q)}"))
|
||||
new_start = self.app.data.s11[start].freq - self.bandwith
|
||||
new_end = self.app.data.s11[end].freq + self.bandwith
|
||||
logger.debug("Single Spot, new scan on %s-%s",
|
||||
new_start, new_end)
|
||||
|
||||
if self.vswr_limit_value > self.vswr_bandwith_value:
|
||||
self.vswr_limit_value = max(
|
||||
self.vswr_bandwith_value, self.vswr_limit_value - 1)
|
||||
|
@ -96,13 +94,14 @@ class MagLoopAnalysis(VSWRAnalysis):
|
|||
else:
|
||||
new_start = new_start - 5 * self.bandwith
|
||||
new_end = new_end + 5 * self.bandwith
|
||||
if all((new_start <= self.min_freq,
|
||||
new_end >= self.max_freq)):
|
||||
if self.vswr_limit_value < 10:
|
||||
self.vswr_limit_value += 2
|
||||
self.input_vswr_limit.setValue(self.vswr_limit_value)
|
||||
logger.debug(
|
||||
"no minimum found, looking for higher value %s", self.vswr_limit_value)
|
||||
if (
|
||||
all((new_start <= self.min_freq, new_end >= self.max_freq))
|
||||
and self.vswr_limit_value < 10
|
||||
):
|
||||
self.vswr_limit_value += 2
|
||||
self.input_vswr_limit.setValue(self.vswr_limit_value)
|
||||
logger.debug(
|
||||
"no minimum found, looking for higher value %s", self.vswr_limit_value)
|
||||
new_start = max(self.min_freq, new_start)
|
||||
new_end = min(self.max_freq, new_end)
|
||||
logger.debug("next search will be %s - %s for vswr %s",
|
||||
|
|
|
@ -109,7 +109,7 @@ class Chart(QtWidgets.QWidget):
|
|||
def __init__(self, name):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.sweepTitle = ""
|
||||
self.sweepTitle = ''
|
||||
|
||||
self.dim = ChartDimensions()
|
||||
self.dragbox = ChartDragBox()
|
||||
|
@ -170,10 +170,14 @@ class Chart(QtWidgets.QWidget):
|
|||
def getActiveMarker(self) -> Marker:
|
||||
if self.draggedMarker is not None:
|
||||
return self.draggedMarker
|
||||
for m in self.markers:
|
||||
if m.isMouseControlledRadioButton.isChecked():
|
||||
return m
|
||||
return None
|
||||
return next(
|
||||
(
|
||||
m
|
||||
for m in self.markers
|
||||
if m.isMouseControlledRadioButton.isChecked()
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
def getNearestMarker(self, x, y) -> Marker:
|
||||
if len(self.data) == 0:
|
||||
|
@ -264,7 +268,6 @@ class Chart(QtWidgets.QWidget):
|
|||
self.swrMarkers.remove(swr)
|
||||
except KeyError:
|
||||
logger.debug("KeyError from %s", self.name)
|
||||
return
|
||||
finally:
|
||||
self.update()
|
||||
|
||||
|
@ -277,8 +280,6 @@ class Chart(QtWidgets.QWidget):
|
|||
cmarker.draw(x, y, color, str(number))
|
||||
|
||||
def drawTitle(self, qp: QtGui.QPainter, position: QtCore.QPoint = None):
|
||||
if not self.sweepTitle:
|
||||
return
|
||||
qp.setPen(Chart.color.text)
|
||||
if position is None:
|
||||
qf = QtGui.QFontMetricsF(self.font())
|
||||
|
|
|
@ -96,11 +96,11 @@ class FrequencyChart(Chart):
|
|||
self.x_menu.addSeparator()
|
||||
|
||||
self.action_set_fixed_start = QtWidgets.QAction(
|
||||
"Start (" + format_frequency_chart(self.minFrequency) + ")")
|
||||
f"Start ({format_frequency_chart(self.minFrequency)})")
|
||||
self.action_set_fixed_start.triggered.connect(self.setMinimumFrequency)
|
||||
|
||||
self.action_set_fixed_stop = QtWidgets.QAction(
|
||||
"Stop (" + format_frequency_chart(self.maxFrequency) + ")")
|
||||
f"Stop ({format_frequency_chart(self.maxFrequency)})")
|
||||
self.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency)
|
||||
|
||||
self.x_menu.addAction(self.action_set_fixed_start)
|
||||
|
|
|
@ -62,64 +62,40 @@ class GroupDelayChart(FrequencyChart):
|
|||
|
||||
def setReference(self, data):
|
||||
self.reference = data
|
||||
|
||||
self.calculateGroupDelay()
|
||||
|
||||
def setData(self, data):
|
||||
self.data = data
|
||||
|
||||
self.calculateGroupDelay()
|
||||
|
||||
def calculateGroupDelay(self):
|
||||
rawData = []
|
||||
for d in self.data:
|
||||
rawData.append(d.phase)
|
||||
|
||||
rawReference = []
|
||||
for d in self.reference:
|
||||
rawReference.append(d.phase)
|
||||
|
||||
if len(self.data) > 1:
|
||||
unwrappedData = np.degrees(np.unwrap(rawData))
|
||||
self.groupDelay = []
|
||||
for i in range(len(self.data)):
|
||||
# TODO: Replace with call to RFTools.groupDelay
|
||||
if i == 0:
|
||||
phase_change = unwrappedData[1] - unwrappedData[0]
|
||||
freq_change = self.data[1].freq - self.data[0].freq
|
||||
elif i == len(self.data)-1:
|
||||
idx = len(self.data)-1
|
||||
phase_change = unwrappedData[idx] - unwrappedData[idx-1]
|
||||
freq_change = self.data[idx].freq - self.data[idx-1].freq
|
||||
else:
|
||||
phase_change = unwrappedData[i+1] - unwrappedData[i-1]
|
||||
freq_change = self.data[i+1].freq - self.data[i-1].freq
|
||||
delay = (-phase_change / (freq_change * 360)) * 10e8
|
||||
if not self.reflective:
|
||||
delay /= 2
|
||||
self.groupDelay.append(delay)
|
||||
|
||||
if len(self.reference) > 1:
|
||||
unwrappedReference = np.degrees(np.unwrap(rawReference))
|
||||
self.groupDelayReference = []
|
||||
for i in range(len(self.reference)):
|
||||
if i == 0:
|
||||
phase_change = unwrappedReference[1] - unwrappedReference[0]
|
||||
freq_change = self.reference[1].freq - self.reference[0].freq
|
||||
elif i == len(self.reference)-1:
|
||||
idx = len(self.reference)-1
|
||||
phase_change = unwrappedReference[idx] - unwrappedReference[idx-1]
|
||||
freq_change = self.reference[idx].freq - self.reference[idx-1].freq
|
||||
else:
|
||||
phase_change = unwrappedReference[i+1] - unwrappedReference[i-1]
|
||||
freq_change = self.reference[i+1].freq - self.reference[i-1].freq
|
||||
delay = (-phase_change / (freq_change * 360)) * 10e8
|
||||
if not self.reflective:
|
||||
delay /= 2
|
||||
self.groupDelayReference.append(delay)
|
||||
|
||||
self.groupDelay = self.calc_data(self.data)
|
||||
self.groupDelayReference = self.calc_data(self.reference)
|
||||
self.update()
|
||||
|
||||
def calc_data(self, data: List[Datapoint]):
|
||||
data_len = len(data)
|
||||
if data_len <= 1:
|
||||
return []
|
||||
unwrapped = np.degrees(np.unwrap([d.phase for d in data]))
|
||||
delay_data = []
|
||||
for i, d in enumerate(data):
|
||||
# TODO: Replace with call to RFTools.groupDelay
|
||||
if i == 0:
|
||||
phase_change = unwrapped[1] - unwrapped[i]
|
||||
freq_change = data[1].freq - d.freq
|
||||
elif i == data_len - 1:
|
||||
phase_change = unwrapped[-1] - unwrapped[-2]
|
||||
freq_change = d.freq - data[-2].freq
|
||||
else:
|
||||
phase_change = unwrapped[i+1] - unwrapped[i-1]
|
||||
freq_change = data[i+1].freq - data[i-1].freq
|
||||
delay = (-phase_change / (freq_change * 360)) * 10e8
|
||||
if not self.reflective:
|
||||
delay /= 2
|
||||
delay_data.append(delay)
|
||||
return delay_data
|
||||
|
||||
def drawValues(self, qp: QtGui.QPainter):
|
||||
if len(self.data) == 0 and len(self.reference) == 0:
|
||||
return
|
||||
|
@ -146,23 +122,19 @@ class GroupDelayChart(FrequencyChart):
|
|||
self.span = span
|
||||
|
||||
tickcount = math.floor(self.dim.height / 60)
|
||||
|
||||
for i in range(tickcount):
|
||||
delay = min_delay + span * i / tickcount
|
||||
y = self.topMargin + round((self.maxDelay - delay) / self.span * self.dim.height)
|
||||
if delay != min_delay and delay != max_delay:
|
||||
if delay not in {min_delay, max_delay}:
|
||||
qp.setPen(QtGui.QPen(Chart.color.text))
|
||||
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"
|
||||
# TODO use format class
|
||||
digits = 0 if delay == 0 else max(
|
||||
0, min(2, math.floor(3 - math.log10(abs(delay)))))
|
||||
delaystr = str(round(delay, digits if digits != 0 else None))
|
||||
qp.drawText(3, y + 3, delaystr)
|
||||
qp.setPen(QtGui.QPen(Chart.color.foreground))
|
||||
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y)
|
||||
|
||||
qp.drawLine(self.leftMargin - 5,
|
||||
self.topMargin,
|
||||
self.leftMargin + self.dim.width,
|
||||
|
@ -179,66 +151,48 @@ class GroupDelayChart(FrequencyChart):
|
|||
|
||||
self.drawFrequencyTicks(qp)
|
||||
|
||||
color = Chart.color.sweep
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(color)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.data)):
|
||||
x = self.getXPosition(self.data[i])
|
||||
y = self.getYPositionFromDelay(self.groupDelay[i])
|
||||
if self.isPlotable(x, y):
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.data[i - 1])
|
||||
prevy = self.getYPositionFromDelay(self.groupDelay[i - 1])
|
||||
qp.setPen(line_pen)
|
||||
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
|
||||
qp.drawLine(x, y, new_x, new_y)
|
||||
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
|
||||
qp.drawLine(prevx, prevy, new_x, new_y)
|
||||
qp.setPen(pen)
|
||||
|
||||
color = Chart.color.reference
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(color)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.reference)):
|
||||
x = self.getXPosition(self.reference[i])
|
||||
y = self.getYPositionFromDelay(self.groupDelayReference[i])
|
||||
if self.isPlotable(x, y):
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.reference[i - 1])
|
||||
prevy = self.getYPositionFromDelay(self.groupDelayReference[i - 1])
|
||||
qp.setPen(line_pen)
|
||||
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
|
||||
qp.drawLine(x, y, new_x, new_y)
|
||||
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
|
||||
qp.drawLine(prevx, prevy, new_x, new_y)
|
||||
qp.setPen(pen)
|
||||
self.draw_data(qp, Chart.color.sweep,
|
||||
self.data, self.groupDelay)
|
||||
self.draw_data(qp, Chart.color.reference,
|
||||
self.reference, self.groupDelayReference)
|
||||
|
||||
self.drawMarkers(qp)
|
||||
|
||||
def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor,
|
||||
data: List[Datapoint], delay: List[Datapoint]):
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(color)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
qp.setPen(pen)
|
||||
for i, d in enumerate(data):
|
||||
x = self.getXPosition(d)
|
||||
y = self.getYPositionFromDelay(delay[i])
|
||||
if self.isPlotable(x, y):
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(data[i - 1])
|
||||
prevy = self.getYPositionFromDelay(delay[i - 1])
|
||||
qp.setPen(line_pen)
|
||||
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
|
||||
qp.drawLine(x, y, new_x, new_y)
|
||||
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
|
||||
qp.drawLine(prevx, prevy, new_x, new_y)
|
||||
qp.setPen(pen)
|
||||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
# TODO: Find a faster way than these expensive "d in self.data" lookups
|
||||
if d in self.data:
|
||||
# TODO: Find a faster way than these expensive "d in data" lookups
|
||||
try:
|
||||
delay = self.groupDelay[self.data.index(d)]
|
||||
elif d in self.reference:
|
||||
delay = self.groupDelayReference[self.reference.index(d)]
|
||||
else:
|
||||
delay = 0
|
||||
except ValueError:
|
||||
try:
|
||||
delay = self.groupDelayReference[self.reference.index(d)]
|
||||
except ValueError:
|
||||
delay = 0
|
||||
return self.getYPositionFromDelay(delay)
|
||||
|
||||
def getYPositionFromDelay(self, delay: float):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# NanoVNASaver
|
||||
#
|
||||
# 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) 2019, 2020 Rune B. Broberg
|
||||
# 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
|
||||
|
@ -16,12 +16,9 @@
|
|||
#
|
||||
# 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 math
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtGui, QtCore
|
||||
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
from NanoVNASaver.Charts.Chart import Chart
|
||||
from NanoVNASaver.Charts.Square import SquareChart
|
||||
|
||||
|
@ -29,129 +26,32 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class PolarChart(SquareChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.dim.width = 250
|
||||
self.dim.height = 250
|
||||
|
||||
self.setMinimumSize(self.dim.width + 40, self.dim.height + 40)
|
||||
pal = QtGui.QPalette()
|
||||
pal.setColor(QtGui.QPalette.Background, Chart.color.background)
|
||||
self.setPalette(pal)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
def paintEvent(self, _: QtGui.QPaintEvent) -> None:
|
||||
qp = QtGui.QPainter(self)
|
||||
self.drawChart(qp)
|
||||
self.drawValues(qp)
|
||||
qp.end()
|
||||
|
||||
def drawChart(self, qp: QtGui.QPainter):
|
||||
centerX = int(self.width()/2)
|
||||
centerY = int(self.height()/2)
|
||||
center_x = int(self.width()/2)
|
||||
center_y = int(self.height()/2)
|
||||
width_2 = int(self.dim.width / 2)
|
||||
height_2 = int(self.dim.height / 2)
|
||||
width_45 = width_2 * 0.7071
|
||||
height_45 = height_2 * 0.7071
|
||||
|
||||
qp.setPen(QtGui.QPen(Chart.color.text))
|
||||
qp.drawText(3, 15, self.name)
|
||||
qp.setPen(QtGui.QPen(Chart.color.foreground))
|
||||
qp.drawEllipse(QtCore.QPoint(centerX, centerY),
|
||||
int(self.dim.width / 2),
|
||||
int(self.dim.height / 2))
|
||||
qp.drawEllipse(QtCore.QPoint(centerX, centerY),
|
||||
int(self.dim.width / 4),
|
||||
int(self.dim.height / 4))
|
||||
qp.drawLine(centerX - int(self.dim.width / 2), centerY,
|
||||
centerX + int(self.dim.width / 2), centerY)
|
||||
qp.drawLine(centerX, centerY - int(self.dim.height / 2),
|
||||
centerX, centerY + int(self.dim.height / 2))
|
||||
qp.drawLine(centerX + int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerY + int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerX - int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerY - int(self.dim.height / 2 * math.sin(math.pi / 4)))
|
||||
qp.drawLine(centerX + int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerY - int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerX - int(self.dim.height / 2 * math.sin(math.pi / 4)),
|
||||
centerY + int(self.dim.height / 2 * math.sin(math.pi / 4)))
|
||||
|
||||
qp.drawEllipse(QtCore.QPoint(center_x, center_y), width_2, height_2)
|
||||
qp.drawEllipse(QtCore.QPoint(center_x, center_y),
|
||||
width_2 // 2, height_2 // 2)
|
||||
|
||||
qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y)
|
||||
qp.drawLine(center_x, center_y - height_2,
|
||||
center_x, center_y + height_2)
|
||||
|
||||
qp.drawLine(center_x + width_45, center_y + height_45,
|
||||
center_x - width_45, center_y - height_45)
|
||||
qp.drawLine(center_x + width_45, center_y - height_45,
|
||||
center_x - width_45, center_y + height_45)
|
||||
|
||||
self.drawTitle(qp)
|
||||
|
||||
def drawValues(self, qp: QtGui.QPainter):
|
||||
if len(self.data) == 0 and len(self.reference) == 0:
|
||||
return
|
||||
pen = QtGui.QPen(Chart.color.sweep)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(Chart.color.sweep)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.data)):
|
||||
x = self.getXPosition(self.data[i])
|
||||
y = self.height()/2 + self.data[i].im * -1 * self.dim.height/2
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.data[i-1])
|
||||
prevy = self.height() / 2 + self.data[i-1].im * -1 * self.dim.height / 2
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
qp.setPen(pen)
|
||||
pen.setColor(Chart.color.reference)
|
||||
line_pen.setColor(Chart.color.reference)
|
||||
qp.setPen(pen)
|
||||
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
|
||||
for i in range(len(self.reference)):
|
||||
data = self.reference[i]
|
||||
if data.freq < fstart or data.freq > fstop:
|
||||
continue
|
||||
x = self.getXPosition(self.reference[i])
|
||||
y = self.height()/2 + data.im * -1 * self.dim.height/2
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.reference[i-1])
|
||||
prevy = self.height() / 2 + self.reference[i-1].im * -1 * self.dim.height / 2
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
qp.setPen(pen)
|
||||
# Now draw the markers
|
||||
for m in self.markers:
|
||||
if m.location != -1 and m.location < len(self.data):
|
||||
x = self.getXPosition(self.data[m.location])
|
||||
y = self.height() / 2 + self.data[m.location].im * -1 * self.dim.height / 2
|
||||
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
|
||||
|
||||
def getXPosition(self, d: Datapoint) -> int:
|
||||
return self.width()/2 + d.re * self.dim.width/2
|
||||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
return self.height()/2 + d.im * -1 * self.dim.height/2
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
if a0.buttons() == QtCore.Qt.RightButton:
|
||||
a0.ignore()
|
||||
return
|
||||
x = a0.x()
|
||||
y = a0.y()
|
||||
absx = x - (self.width() - self.dim.width) / 2
|
||||
absy = y - (self.height() - self.dim.height) / 2
|
||||
if absx < 0 or absx > self.dim.width or absy < 0 or absy > self.dim.height \
|
||||
or len(self.data) == len(self.reference) == 0:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
|
||||
if len(self.data) > 0:
|
||||
target = self.data
|
||||
else:
|
||||
target = self.reference
|
||||
positions = []
|
||||
for d in target:
|
||||
thisx = self.width() / 2 + d.re * self.dim.width / 2
|
||||
thisy = self.height() / 2 + d.im * -1 * self.dim.height / 2
|
||||
positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2))
|
||||
|
||||
minimum_position = positions.index(min(positions))
|
||||
m = self.getActiveMarker()
|
||||
if m is not None:
|
||||
m.setFrequency(str(round(target[minimum_position].freq)))
|
||||
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
|
||||
return
|
||||
def zoomTo(self, x1, y1, x2, y2):
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# NanoVNASaver
|
||||
#
|
||||
# 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) 2019, 2020 Rune B. Broberg
|
||||
# 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
|
||||
|
@ -16,12 +16,9 @@
|
|||
#
|
||||
# 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 math
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtGui, QtCore
|
||||
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
from NanoVNASaver.Charts.Chart import Chart
|
||||
from NanoVNASaver.Charts.Square import SquareChart
|
||||
|
||||
|
@ -29,25 +26,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class SmithChart(SquareChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.dim.width = 250
|
||||
self.dim.height = 250
|
||||
|
||||
self.setMinimumSize(self.dim.width + 40, self.dim.height + 40)
|
||||
pal = QtGui.QPalette()
|
||||
pal.setColor(QtGui.QPalette.Background, Chart.color.background)
|
||||
self.setPalette(pal)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
def paintEvent(self, _: QtGui.QPaintEvent) -> None:
|
||||
qp = QtGui.QPainter(self)
|
||||
# qp.begin(self) # Apparently not needed?
|
||||
self.drawSmithChart(qp)
|
||||
self.drawValues(qp)
|
||||
qp.end()
|
||||
|
||||
def drawSmithChart(self, qp: QtGui.QPainter):
|
||||
def drawChart(self, qp: QtGui.QPainter) -> None:
|
||||
centerX = int(self.width()/2)
|
||||
centerY = int(self.height()/2)
|
||||
qp.setPen(QtGui.QPen(Chart.color.text))
|
||||
|
@ -116,91 +95,5 @@ class SmithChart(SquareChart):
|
|||
QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20),
|
||||
QtCore.Qt.AlignCenter, str(swr))
|
||||
|
||||
def drawValues(self, qp: QtGui.QPainter):
|
||||
if len(self.data) == 0 and len(self.reference) == 0:
|
||||
return
|
||||
pen = QtGui.QPen(Chart.color.sweep)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(Chart.color.sweep)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.data)):
|
||||
x = self.getXPosition(self.data[i])
|
||||
y = int(self.height()/2 + self.data[i].im * -1 * self.dim.height/2)
|
||||
qp.drawPoint(x, y)
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.data[i-1])
|
||||
prevy = int(self.height() / 2 + self.data[i-1].im * -1 * self.dim.height / 2)
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
qp.setPen(pen)
|
||||
pen.setColor(Chart.color.reference)
|
||||
line_pen.setColor(Chart.color.reference)
|
||||
qp.setPen(pen)
|
||||
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
|
||||
for i in range(len(self.reference)):
|
||||
data = self.reference[i]
|
||||
if data.freq < fstart or data.freq > fstop:
|
||||
continue
|
||||
x = self.getXPosition(data)
|
||||
y = int(self.height()/2 + data.im * -1 * self.dim.height/2)
|
||||
qp.drawPoint(x, y)
|
||||
if self.flag.draw_lines and i > 0:
|
||||
prevx = self.getXPosition(self.reference[i-1])
|
||||
prevy = int(self.height() / 2 + self.reference[i-1].im * -1 * self.dim.height / 2)
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
qp.setPen(pen)
|
||||
# Now draw the markers
|
||||
for m in self.markers:
|
||||
if m.location != -1:
|
||||
x = self.getXPosition(self.data[m.location])
|
||||
y = self.height() / 2 + self.data[m.location].im * -1 * self.dim.height / 2
|
||||
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
|
||||
|
||||
def getXPosition(self, d: Datapoint) -> int:
|
||||
return int(self.width()/2 + d.re * self.dim.width/2)
|
||||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
return int(self.height()/2 + d.im * -1 * self.dim.height/2)
|
||||
|
||||
def heightForWidth(self, a0: int) -> int:
|
||||
return a0
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
if a0.buttons() == QtCore.Qt.RightButton:
|
||||
a0.ignore()
|
||||
return
|
||||
x = a0.x()
|
||||
y = a0.y()
|
||||
absx = x - (self.width() - self.dim.width) / 2
|
||||
absy = y - (self.height() - self.dim.height) / 2
|
||||
if absx < 0 or absx > self.dim.width or absy < 0 or absy > self.dim.height \
|
||||
or len(self.data) == len(self.reference) == 0:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
|
||||
if len(self.data) > 0:
|
||||
target = self.data
|
||||
else:
|
||||
target = self.reference
|
||||
positions = []
|
||||
for d in target:
|
||||
thisx = self.width() / 2 + d.re * self.dim.width / 2
|
||||
thisy = self.height() / 2 + d.im * -1 * self.dim.height / 2
|
||||
positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2))
|
||||
|
||||
minimum_position = positions.index(min(positions))
|
||||
m = self.getActiveMarker()
|
||||
if m is not None:
|
||||
m.setFrequency(str(round(target[minimum_position].freq)))
|
||||
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
|
||||
return
|
||||
def zoomTo(self, x1, y1, x2, y2):
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -17,23 +17,81 @@
|
|||
# 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 logging
|
||||
import math
|
||||
from typing import List
|
||||
|
||||
from PyQt5 import QtGui, QtCore
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
|
||||
from NanoVNASaver.Charts.Chart import Chart
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SquareChart(Chart):
|
||||
def __init__(self, name):
|
||||
def __init__(self, name=''):
|
||||
super().__init__(name)
|
||||
sizepolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Fixed,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
self.setSizePolicy(sizepolicy)
|
||||
self.dim.width = self.width()-40
|
||||
self.dim.height = self.height()-40
|
||||
self.dim.width = 250
|
||||
self.dim.height = 250
|
||||
self.setMinimumSize(self.dim.width + 40, self.dim.height + 40)
|
||||
|
||||
pal = QtGui.QPalette()
|
||||
pal.setColor(QtGui.QPalette.Background, Chart.color.background)
|
||||
self.setPalette(pal)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
def paintEvent(self, _: QtGui.QPaintEvent) -> None:
|
||||
qp = QtGui.QPainter(self)
|
||||
self.drawChart(qp)
|
||||
self.drawValues(qp)
|
||||
qp.end()
|
||||
|
||||
def drawChart(self, qp: QtGui.QPainter) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor,
|
||||
data: List[Datapoint], fstart: int=0, fstop: int=0):
|
||||
if not data:
|
||||
return
|
||||
fstop = fstop or data[-1].freq
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidth(self.dim.point)
|
||||
line_pen = QtGui.QPen(color)
|
||||
line_pen.setWidth(self.dim.line)
|
||||
|
||||
qp.setPen(pen)
|
||||
prev_x = self.getXPosition(data[0])
|
||||
prev_y = int(self.height() / 2 + data[0].im * -1 * self.dim.height / 2)
|
||||
for i, d in enumerate(data):
|
||||
x = self.getXPosition(d)
|
||||
y = int(self.height()/2 + d.im * -1 * self.dim.height/2)
|
||||
if d.freq > fstart and d.freq < fstop:
|
||||
qp.drawPoint(x, y)
|
||||
if self.flag.draw_lines and i > 0:
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prev_x, prev_y)
|
||||
qp.setPen(pen)
|
||||
prev_x, prev_y = x, y
|
||||
|
||||
def drawValues(self, qp: QtGui.QPainter):
|
||||
if not (self.data or self.reference):
|
||||
return
|
||||
self.draw_data(qp, Chart.color.sweep, self.data)
|
||||
|
||||
fstart = self.data[0].freq if self.data else 0
|
||||
fstop = self.data[-1].freq if self.data else 0
|
||||
self.draw_data(qp, Chart.color.reference, self.reference, fstart, fstop)
|
||||
|
||||
for m in self.markers:
|
||||
if m.location != -1 and m.location < len(self.data):
|
||||
x = self.getXPosition(self.data[m.location])
|
||||
y = self.height() / 2 + self.data[m.location].im * -1 * self.dim.height / 2
|
||||
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
|
||||
|
||||
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
|
||||
if not self.flag.is_popout:
|
||||
|
@ -44,3 +102,44 @@ class SquareChart(Chart):
|
|||
min_dimension = min(a0.size().height(), a0.size().width())
|
||||
self.dim.width = self.dim.height = min_dimension - 40
|
||||
self.update()
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent):
|
||||
if a0.buttons() == QtCore.Qt.RightButton:
|
||||
a0.ignore()
|
||||
return
|
||||
|
||||
x = a0.x()
|
||||
y = a0.y()
|
||||
absx = x - (self.width() - self.dim.width) / 2
|
||||
absy = y - (self.height() - self.dim.height) / 2
|
||||
if absx < 0 or absx > self.dim.width or absy < 0 or absy > self.dim.height \
|
||||
or len(self.data) == len(self.reference) == 0:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
|
||||
target = self.data or self.reference
|
||||
positions = []
|
||||
|
||||
dim_x_2 = self.dim.width / 2
|
||||
dim_y_2 = self.dim.height / 2
|
||||
width_2 = self.width() / 2
|
||||
height_2 = self.height() / 2
|
||||
|
||||
positions = [
|
||||
math.sqrt(
|
||||
(x - (width_2 + d.re * dim_x_2))**2 +
|
||||
(y - (height_2 - d.im * dim_y_2))**2)
|
||||
for d in target
|
||||
]
|
||||
|
||||
minimum_position = positions.index(min(positions))
|
||||
if m := self.getActiveMarker():
|
||||
m.setFrequency(str(round(target[minimum_position].freq)))
|
||||
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
|
||||
|
||||
def getXPosition(self, d: Datapoint) -> int:
|
||||
return int(self.width()/2 + d.re * self.dim.width/2)
|
||||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
return int(self.height()/2 + d.im * -1 * self.dim.height/2)
|
||||
|
|
|
@ -34,6 +34,7 @@ from NanoVNASaver.Hardware.NanoVNA_H4 import NanoVNA_H4
|
|||
from NanoVNASaver.Hardware.NanoVNA_V2 import NanoVNA_V2
|
||||
from NanoVNASaver.Hardware.TinySA import TinySA
|
||||
from NanoVNASaver.Hardware.Serial import drain_serial, Interface
|
||||
from NanoVNASaver.Hardware.VNA import VNA
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
@ -41,7 +41,7 @@ def drain_serial(serial_port: serial.Serial):
|
|||
class Interface(serial.Serial):
|
||||
def __init__(self, interface_type: str, comment, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
assert interface_type in ('serial', 'usb', 'bt', 'network')
|
||||
assert interface_type in {'serial', 'usb', 'bt', 'network'}
|
||||
self.type = interface_type
|
||||
self.comment = comment
|
||||
self.port = None
|
||||
|
|
|
@ -38,7 +38,7 @@ class TinySA(VNA):
|
|||
|
||||
def __init__(self, iface: Interface):
|
||||
super().__init__(iface)
|
||||
self.features = set(('Screenshots',))
|
||||
self.features = {'Screenshots'}
|
||||
logger.debug("Setting initial start,stop")
|
||||
self.start, self.stop = self._get_running_frequencies()
|
||||
self.sweep_max_freq_Hz = 950e6
|
||||
|
|
|
@ -77,15 +77,8 @@ class DeltaMarker(Marker):
|
|||
RFTools.impedance_to_inductance(imp_p_b, s11_b.freq)-
|
||||
RFTools.impedance_to_inductance(imp_p_a, s11_a.freq))
|
||||
|
||||
if imp.imag < 0:
|
||||
x_str = cap_str
|
||||
else:
|
||||
x_str = ind_str
|
||||
|
||||
if imp_p.imag < 0:
|
||||
x_p_str = cap_p_str
|
||||
else:
|
||||
x_p_str = ind_p_str
|
||||
x_str = cap_str if imp.imag < 0 else ind_str
|
||||
x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str
|
||||
|
||||
self.label['actualfreq'].setText(
|
||||
format_frequency_space(s11_b.freq - s11_a.freq))
|
||||
|
|
|
@ -84,9 +84,9 @@ class Value():
|
|||
if s21:
|
||||
s21 = [s21[0], ] + s21
|
||||
if index == len(s11):
|
||||
s11 = s11 + [s11[-1], ]
|
||||
s11 += [s11[-1], ]
|
||||
if s21:
|
||||
s21 = s21 + [s21[-1], ]
|
||||
s21 += [s21[-1], ]
|
||||
self.freq = s11[1].freq
|
||||
self.s11 = s11[index-1:index+2]
|
||||
if s21:
|
||||
|
|
|
@ -271,7 +271,7 @@ class Marker(QtCore.QObject, Value):
|
|||
self.location = i-1
|
||||
if i < datasize:
|
||||
self.frequencyInput.nextFrequency = item.freq
|
||||
if (i-2) >= 0:
|
||||
if i >= 2:
|
||||
self.frequencyInput.previousFrequency = data[i-2].freq
|
||||
return
|
||||
# If we still didn't find a best spot, it was the last value
|
||||
|
@ -317,15 +317,8 @@ class Marker(QtCore.QObject, Value):
|
|||
ind_p_str = format_inductance(
|
||||
RFTools.impedance_to_inductance(imp_p, _s11.freq))
|
||||
|
||||
if imp.imag < 0:
|
||||
x_str = cap_str
|
||||
else:
|
||||
x_str = ind_str
|
||||
|
||||
if imp_p.imag < 0:
|
||||
x_p_str = cap_p_str
|
||||
else:
|
||||
x_p_str = ind_p_str
|
||||
x_str = cap_str if imp.imag < 0 else ind_str
|
||||
x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str
|
||||
|
||||
self.label['actualfreq'].setText(format_frequency_space(_s11.freq))
|
||||
self.label['lambda'].setText(format_wavelength(_s11.wavelength))
|
||||
|
@ -342,7 +335,9 @@ class Marker(QtCore.QObject, Value):
|
|||
self.label['s11mag'].setText(format_magnitude(abs(_s11.z)))
|
||||
self.label['s11phase'].setText(format_phase(_s11.phase))
|
||||
self.label['s11polar'].setText(
|
||||
str(round(abs(_s11.z), 2)) + "∠" + format_phase(_s11.phase))
|
||||
f'{str(round(abs(_s11.z), 2))}∠{format_phase(_s11.phase)}'
|
||||
)
|
||||
|
||||
self.label['s11q'].setText(format_q_factor(_s11.qFactor()))
|
||||
self.label['s11z'].setText(format_resistance(abs(imp)))
|
||||
self.label['serc'].setText(cap_str)
|
||||
|
@ -359,7 +354,9 @@ class Marker(QtCore.QObject, Value):
|
|||
self.label['s21mag'].setText(format_magnitude(abs(_s21.z)))
|
||||
self.label['s21phase'].setText(format_phase(_s21.phase))
|
||||
self.label['s21polar'].setText(
|
||||
str(round(abs(_s21.z), 2)) + "∠" + format_phase(_s21.phase))
|
||||
f'{str(round(abs(_s21.z), 2))}∠{format_phase(_s21.phase)}'
|
||||
)
|
||||
|
||||
self.label['s21magshunt'].setText(
|
||||
format_magnitude(abs(_s21.shuntImpedance())))
|
||||
self.label['s21magseries'].setText(
|
||||
|
|
|
@ -16,6 +16,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/>.
|
||||
import contextlib
|
||||
import logging
|
||||
import typing
|
||||
|
||||
|
@ -132,10 +133,8 @@ class BandsModel(QtCore.QAbstractTableModel):
|
|||
orientation: QtCore.Qt.Orientation, role: int = ...):
|
||||
if (role == QtCore.Qt.DisplayRole and
|
||||
orientation == QtCore.Qt.Horizontal):
|
||||
try:
|
||||
with contextlib.suppress(IndexError):
|
||||
return _HEADER_DATA[section]
|
||||
except IndexError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:
|
||||
|
|
|
@ -192,12 +192,10 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if not self.app.calibration.isCalculated:
|
||||
data11 = raw_data11.copy()
|
||||
data21 = raw_data21.copy()
|
||||
elif self.app.calibration.isValid1Port():
|
||||
data11.extend(self.app.calibration.correct11(dp) for dp in raw_data11)
|
||||
else:
|
||||
if self.app.calibration.isValid1Port():
|
||||
for dp in raw_data11:
|
||||
data11.append(self.app.calibration.correct11(dp))
|
||||
else:
|
||||
data11 = raw_data11.copy()
|
||||
data11 = raw_data11.copy()
|
||||
|
||||
if self.app.calibration.isValid2Port():
|
||||
for counter, dp in enumerate(raw_data21):
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
app-id: io.github.zarath.nanovna-saver
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: '5.15-21.08'
|
||||
sdk: org.kde.Sdk
|
||||
command: /app/bin/NanoVNASaver
|
||||
build-options:
|
||||
build-args:
|
||||
- --share=network
|
||||
modules:
|
||||
- name: nanonva-saver
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --prefix=/app wheel
|
||||
- pip3 install --prefix=/app git+https://github.com/NanoVNA-Saver/nanovna-saver.git
|
||||
finish-args:
|
||||
# X11 + XShm access
|
||||
- --share=ipc
|
||||
- --socket=x11
|
||||
# Wayland access
|
||||
- --socket=wayland
|
||||
# Needs access to NanoVNAs
|
||||
- --device=all
|
||||
# Needs to save files locally
|
||||
- --filesystem=xdg-documents
|
|
@ -13,7 +13,7 @@ packages = find_namespace:
|
|||
install_requires=
|
||||
pyserial>=3.5
|
||||
PyQt5>=5.15.0
|
||||
numpy>=1.21.1,<1.22.0
|
||||
numpy>=1.21.1
|
||||
scipy>=1.7.1
|
||||
Cython>=0.29.24
|
||||
python_requires = >=3.8, <4
|
||||
|
|
Ładowanie…
Reference in New Issue