Feature/linting 220402 (#499)

* added .flatpak-builder to .gitingnore
* GroupDelay Chart simplified
* allow numpy > 1.21 fixes #456
* Added flatpak manifest
pull/504/head
Holger Müller 2022-05-14 11:00:34 +02:00 zatwierdzone przez GitHub
rodzic 140ce4906c
commit 6aa7aaa051
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 278 dodań i 419 usunięć

3
.gitignore vendored
Wyświetl plik

@ -11,4 +11,5 @@
settings.json settings.json
.gitignore .gitignore
.coverage .coverage
/nanovna-saver.exe.spec .flatpak-builder
/nanovna-saver.exe.spec

Wyświetl plik

@ -70,23 +70,21 @@ class MagLoopAnalysis(VSWRAnalysis):
if len(self.minimums) == 1: if len(self.minimums) == 1:
m = self.minimums[0] m = self.minimums[0]
start, lowest, end = m start, lowest, end = m
if start != end: 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:
new_start = self.app.data.s11[start].freq - 2 * self.bandwith new_start = self.app.data.s11[start].freq - 2 * self.bandwith
new_end = self.app.data.s11[end].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) 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: if self.vswr_limit_value > self.vswr_bandwith_value:
self.vswr_limit_value = max( self.vswr_limit_value = max(
self.vswr_bandwith_value, self.vswr_limit_value - 1) self.vswr_bandwith_value, self.vswr_limit_value - 1)
@ -96,13 +94,14 @@ class MagLoopAnalysis(VSWRAnalysis):
else: else:
new_start = new_start - 5 * self.bandwith new_start = new_start - 5 * self.bandwith
new_end = new_end + 5 * self.bandwith new_end = new_end + 5 * self.bandwith
if all((new_start <= self.min_freq, if (
new_end >= self.max_freq)): all((new_start <= self.min_freq, new_end >= self.max_freq))
if self.vswr_limit_value < 10: and self.vswr_limit_value < 10
self.vswr_limit_value += 2 ):
self.input_vswr_limit.setValue(self.vswr_limit_value) self.vswr_limit_value += 2
logger.debug( self.input_vswr_limit.setValue(self.vswr_limit_value)
"no minimum found, looking for higher value %s", 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_start = max(self.min_freq, new_start)
new_end = min(self.max_freq, new_end) new_end = min(self.max_freq, new_end)
logger.debug("next search will be %s - %s for vswr %s", logger.debug("next search will be %s - %s for vswr %s",

Wyświetl plik

@ -109,7 +109,7 @@ class Chart(QtWidgets.QWidget):
def __init__(self, name): def __init__(self, name):
super().__init__() super().__init__()
self.name = name self.name = name
self.sweepTitle = "" self.sweepTitle = ''
self.dim = ChartDimensions() self.dim = ChartDimensions()
self.dragbox = ChartDragBox() self.dragbox = ChartDragBox()
@ -170,10 +170,14 @@ class Chart(QtWidgets.QWidget):
def getActiveMarker(self) -> Marker: def getActiveMarker(self) -> Marker:
if self.draggedMarker is not None: if self.draggedMarker is not None:
return self.draggedMarker return self.draggedMarker
for m in self.markers: return next(
if m.isMouseControlledRadioButton.isChecked(): (
return m m
return None for m in self.markers
if m.isMouseControlledRadioButton.isChecked()
),
None,
)
def getNearestMarker(self, x, y) -> Marker: def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0: if len(self.data) == 0:
@ -264,7 +268,6 @@ class Chart(QtWidgets.QWidget):
self.swrMarkers.remove(swr) self.swrMarkers.remove(swr)
except KeyError: except KeyError:
logger.debug("KeyError from %s", self.name) logger.debug("KeyError from %s", self.name)
return
finally: finally:
self.update() self.update()
@ -277,8 +280,6 @@ class Chart(QtWidgets.QWidget):
cmarker.draw(x, y, color, str(number)) cmarker.draw(x, y, color, str(number))
def drawTitle(self, qp: QtGui.QPainter, position: QtCore.QPoint = None): def drawTitle(self, qp: QtGui.QPainter, position: QtCore.QPoint = None):
if not self.sweepTitle:
return
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
if position is None: if position is None:
qf = QtGui.QFontMetricsF(self.font()) qf = QtGui.QFontMetricsF(self.font())

Wyświetl plik

@ -96,11 +96,11 @@ class FrequencyChart(Chart):
self.x_menu.addSeparator() self.x_menu.addSeparator()
self.action_set_fixed_start = QtWidgets.QAction( 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_start.triggered.connect(self.setMinimumFrequency)
self.action_set_fixed_stop = QtWidgets.QAction( 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.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency)
self.x_menu.addAction(self.action_set_fixed_start) self.x_menu.addAction(self.action_set_fixed_start)

Wyświetl plik

@ -62,64 +62,40 @@ class GroupDelayChart(FrequencyChart):
def setReference(self, data): def setReference(self, data):
self.reference = data self.reference = data
self.calculateGroupDelay() self.calculateGroupDelay()
def setData(self, data): def setData(self, data):
self.data = data self.data = data
self.calculateGroupDelay() self.calculateGroupDelay()
def calculateGroupDelay(self): def calculateGroupDelay(self):
rawData = [] self.groupDelay = self.calc_data(self.data)
for d in self.data: self.groupDelayReference = self.calc_data(self.reference)
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.update() 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): def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0: if len(self.data) == 0 and len(self.reference) == 0:
return return
@ -146,23 +122,19 @@ class GroupDelayChart(FrequencyChart):
self.span = span self.span = span
tickcount = math.floor(self.dim.height / 60) tickcount = math.floor(self.dim.height / 60)
for i in range(tickcount): for i in range(tickcount):
delay = min_delay + span * i / tickcount delay = min_delay + span * i / tickcount
y = self.topMargin + round((self.maxDelay - delay) / self.span * self.dim.height) 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)) qp.setPen(QtGui.QPen(Chart.color.text))
if delay != 0: # TODO use format class
digits = max(0, min(2, math.floor(3 - math.log10(abs(delay))))) digits = 0 if delay == 0 else max(
if digits == 0: 0, min(2, math.floor(3 - math.log10(abs(delay)))))
delaystr = str(round(delay)) delaystr = str(round(delay, digits if digits != 0 else None))
else:
delaystr = str(round(delay, digits))
else:
delaystr = "0"
qp.drawText(3, y + 3, delaystr) qp.drawText(3, y + 3, delaystr)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y)
qp.drawLine(self.leftMargin - 5, qp.drawLine(self.leftMargin - 5,
self.topMargin, self.topMargin,
self.leftMargin + self.dim.width, self.leftMargin + self.dim.width,
@ -179,66 +151,48 @@ class GroupDelayChart(FrequencyChart):
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
color = Chart.color.sweep self.draw_data(qp, Chart.color.sweep,
pen = QtGui.QPen(color) self.data, self.groupDelay)
pen.setWidth(self.dim.point) self.draw_data(qp, Chart.color.reference,
line_pen = QtGui.QPen(color) self.reference, self.groupDelayReference)
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.drawMarkers(qp) 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: def getYPosition(self, d: Datapoint) -> int:
# TODO: Find a faster way than these expensive "d in self.data" lookups # TODO: Find a faster way than these expensive "d in data" lookups
if d in self.data: try:
delay = self.groupDelay[self.data.index(d)] delay = self.groupDelay[self.data.index(d)]
elif d in self.reference: except ValueError:
delay = self.groupDelayReference[self.reference.index(d)] try:
else: delay = self.groupDelayReference[self.reference.index(d)]
delay = 0 except ValueError:
delay = 0
return self.getYPositionFromDelay(delay) return self.getYPositionFromDelay(delay)
def getYPositionFromDelay(self, delay: float): def getYPositionFromDelay(self, delay: float):

Wyświetl plik

@ -1,8 +1,8 @@
# NanoVNASaver # NanoVNASaver
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg # Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors # Copyright (C) 2020ff NanoVNA-Saver Authors
# #
# This program is free software: you can redistribute it and/or modify # 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 # 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math
import logging import logging
from PyQt5 import QtGui, QtCore from PyQt5 import QtGui, QtCore
from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Square import SquareChart from NanoVNASaver.Charts.Square import SquareChart
@ -29,129 +26,32 @@ logger = logging.getLogger(__name__)
class PolarChart(SquareChart): 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): def drawChart(self, qp: QtGui.QPainter):
centerX = int(self.width()/2) center_x = int(self.width()/2)
centerY = int(self.height()/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.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(3, 15, self.name) qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawEllipse(QtCore.QPoint(centerX, centerY),
int(self.dim.width / 2), qp.drawEllipse(QtCore.QPoint(center_x, center_y), width_2, height_2)
int(self.dim.height / 2)) qp.drawEllipse(QtCore.QPoint(center_x, center_y),
qp.drawEllipse(QtCore.QPoint(centerX, centerY), width_2 // 2, height_2 // 2)
int(self.dim.width / 4),
int(self.dim.height / 4)) qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y)
qp.drawLine(centerX - int(self.dim.width / 2), centerY, qp.drawLine(center_x, center_y - height_2,
centerX + int(self.dim.width / 2), centerY) center_x, center_y + height_2)
qp.drawLine(centerX, centerY - int(self.dim.height / 2),
centerX, centerY + int(self.dim.height / 2)) qp.drawLine(center_x + width_45, center_y + height_45,
qp.drawLine(centerX + int(self.dim.height / 2 * math.sin(math.pi / 4)), center_x - width_45, center_y - height_45)
centerY + int(self.dim.height / 2 * math.sin(math.pi / 4)), qp.drawLine(center_x + width_45, center_y - height_45,
centerX - int(self.dim.height / 2 * math.sin(math.pi / 4)), center_x - width_45, center_y + height_45)
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)))
self.drawTitle(qp) self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter): def zoomTo(self, x1, y1, x2, y2):
if len(self.data) == 0 and len(self.reference) == 0: raise NotImplementedError()
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

Wyświetl plik

@ -1,8 +1,8 @@
# NanoVNASaver # NanoVNASaver
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg # Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors # Copyright (C) 2020ff NanoVNA-Saver Authors
# #
# This program is free software: you can redistribute it and/or modify # 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 # 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math
import logging import logging
from PyQt5 import QtGui, QtCore from PyQt5 import QtGui, QtCore
from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Square import SquareChart from NanoVNASaver.Charts.Square import SquareChart
@ -29,25 +26,7 @@ logger = logging.getLogger(__name__)
class SmithChart(SquareChart): class SmithChart(SquareChart):
def __init__(self, name=""): def drawChart(self, qp: QtGui.QPainter) -> None:
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):
centerX = int(self.width()/2) centerX = int(self.width()/2)
centerY = int(self.height()/2) centerY = int(self.height()/2)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
@ -116,91 +95,5 @@ class SmithChart(SquareChart):
QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20), QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20),
QtCore.Qt.AlignCenter, str(swr)) QtCore.Qt.AlignCenter, str(swr))
def drawValues(self, qp: QtGui.QPainter): def zoomTo(self, x1, y1, x2, y2):
if len(self.data) == 0 and len(self.reference) == 0: raise NotImplementedError()
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

Wyświetl plik

@ -17,23 +17,81 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging import logging
import math
from typing import List
from PyQt5 import QtGui, QtCore
from PyQt5 import QtWidgets, QtGui from PyQt5 import QtWidgets, QtGui
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.RFTools import Datapoint
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SquareChart(Chart): class SquareChart(Chart):
def __init__(self, name): def __init__(self, name=''):
super().__init__(name) super().__init__(name)
sizepolicy = QtWidgets.QSizePolicy( sizepolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed,
QtWidgets.QSizePolicy.MinimumExpanding) QtWidgets.QSizePolicy.MinimumExpanding)
self.setSizePolicy(sizepolicy) self.setSizePolicy(sizepolicy)
self.dim.width = self.width()-40 self.dim.width = 250
self.dim.height = self.height()-40 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: def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
if not self.flag.is_popout: if not self.flag.is_popout:
@ -44,3 +102,44 @@ class SquareChart(Chart):
min_dimension = min(a0.size().height(), a0.size().width()) min_dimension = min(a0.size().height(), a0.size().width())
self.dim.width = self.dim.height = min_dimension - 40 self.dim.width = self.dim.height = min_dimension - 40
self.update() 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)

Wyświetl plik

@ -34,6 +34,7 @@ from NanoVNASaver.Hardware.NanoVNA_H4 import NanoVNA_H4
from NanoVNASaver.Hardware.NanoVNA_V2 import NanoVNA_V2 from NanoVNASaver.Hardware.NanoVNA_V2 import NanoVNA_V2
from NanoVNASaver.Hardware.TinySA import TinySA from NanoVNASaver.Hardware.TinySA import TinySA
from NanoVNASaver.Hardware.Serial import drain_serial, Interface from NanoVNASaver.Hardware.Serial import drain_serial, Interface
from NanoVNASaver.Hardware.VNA import VNA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

Wyświetl plik

@ -41,7 +41,7 @@ def drain_serial(serial_port: serial.Serial):
class Interface(serial.Serial): class Interface(serial.Serial):
def __init__(self, interface_type: str, comment, *args, **kwargs): def __init__(self, interface_type: str, comment, *args, **kwargs):
super().__init__(*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.type = interface_type
self.comment = comment self.comment = comment
self.port = None self.port = None

Wyświetl plik

@ -38,7 +38,7 @@ class TinySA(VNA):
def __init__(self, iface: Interface): def __init__(self, iface: Interface):
super().__init__(iface) super().__init__(iface)
self.features = set(('Screenshots',)) self.features = {'Screenshots'}
logger.debug("Setting initial start,stop") logger.debug("Setting initial start,stop")
self.start, self.stop = self._get_running_frequencies() self.start, self.stop = self._get_running_frequencies()
self.sweep_max_freq_Hz = 950e6 self.sweep_max_freq_Hz = 950e6

Wyświetl plik

@ -77,15 +77,8 @@ class DeltaMarker(Marker):
RFTools.impedance_to_inductance(imp_p_b, s11_b.freq)- RFTools.impedance_to_inductance(imp_p_b, s11_b.freq)-
RFTools.impedance_to_inductance(imp_p_a, s11_a.freq)) RFTools.impedance_to_inductance(imp_p_a, s11_a.freq))
if imp.imag < 0: x_str = cap_str if imp.imag < 0 else ind_str
x_str = cap_str x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str
else:
x_str = ind_str
if imp_p.imag < 0:
x_p_str = cap_p_str
else:
x_p_str = ind_p_str
self.label['actualfreq'].setText( self.label['actualfreq'].setText(
format_frequency_space(s11_b.freq - s11_a.freq)) format_frequency_space(s11_b.freq - s11_a.freq))

Wyświetl plik

@ -84,9 +84,9 @@ class Value():
if s21: if s21:
s21 = [s21[0], ] + s21 s21 = [s21[0], ] + s21
if index == len(s11): if index == len(s11):
s11 = s11 + [s11[-1], ] s11 += [s11[-1], ]
if s21: if s21:
s21 = s21 + [s21[-1], ] s21 += [s21[-1], ]
self.freq = s11[1].freq self.freq = s11[1].freq
self.s11 = s11[index-1:index+2] self.s11 = s11[index-1:index+2]
if s21: if s21:

Wyświetl plik

@ -271,7 +271,7 @@ class Marker(QtCore.QObject, Value):
self.location = i-1 self.location = i-1
if i < datasize: if i < datasize:
self.frequencyInput.nextFrequency = item.freq self.frequencyInput.nextFrequency = item.freq
if (i-2) >= 0: if i >= 2:
self.frequencyInput.previousFrequency = data[i-2].freq self.frequencyInput.previousFrequency = data[i-2].freq
return return
# If we still didn't find a best spot, it was the last value # 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( ind_p_str = format_inductance(
RFTools.impedance_to_inductance(imp_p, _s11.freq)) RFTools.impedance_to_inductance(imp_p, _s11.freq))
if imp.imag < 0: x_str = cap_str if imp.imag < 0 else ind_str
x_str = cap_str x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str
else:
x_str = ind_str
if imp_p.imag < 0:
x_p_str = cap_p_str
else:
x_p_str = ind_p_str
self.label['actualfreq'].setText(format_frequency_space(_s11.freq)) self.label['actualfreq'].setText(format_frequency_space(_s11.freq))
self.label['lambda'].setText(format_wavelength(_s11.wavelength)) 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['s11mag'].setText(format_magnitude(abs(_s11.z)))
self.label['s11phase'].setText(format_phase(_s11.phase)) self.label['s11phase'].setText(format_phase(_s11.phase))
self.label['s11polar'].setText( 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['s11q'].setText(format_q_factor(_s11.qFactor()))
self.label['s11z'].setText(format_resistance(abs(imp))) self.label['s11z'].setText(format_resistance(abs(imp)))
self.label['serc'].setText(cap_str) 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['s21mag'].setText(format_magnitude(abs(_s21.z)))
self.label['s21phase'].setText(format_phase(_s21.phase)) self.label['s21phase'].setText(format_phase(_s21.phase))
self.label['s21polar'].setText( 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( self.label['s21magshunt'].setText(
format_magnitude(abs(_s21.shuntImpedance()))) format_magnitude(abs(_s21.shuntImpedance())))
self.label['s21magseries'].setText( self.label['s21magseries'].setText(

Wyświetl plik

@ -16,6 +16,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import contextlib
import logging import logging
import typing import typing
@ -132,10 +133,8 @@ class BandsModel(QtCore.QAbstractTableModel):
orientation: QtCore.Qt.Orientation, role: int = ...): orientation: QtCore.Qt.Orientation, role: int = ...):
if (role == QtCore.Qt.DisplayRole and if (role == QtCore.Qt.DisplayRole and
orientation == QtCore.Qt.Horizontal): orientation == QtCore.Qt.Horizontal):
try: with contextlib.suppress(IndexError):
return _HEADER_DATA[section] return _HEADER_DATA[section]
except IndexError:
pass
return None return None
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags: def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:

Wyświetl plik

@ -192,12 +192,10 @@ class SweepWorker(QtCore.QRunnable):
if not self.app.calibration.isCalculated: if not self.app.calibration.isCalculated:
data11 = raw_data11.copy() data11 = raw_data11.copy()
data21 = raw_data21.copy() data21 = raw_data21.copy()
elif self.app.calibration.isValid1Port():
data11.extend(self.app.calibration.correct11(dp) for dp in raw_data11)
else: else:
if self.app.calibration.isValid1Port(): data11 = raw_data11.copy()
for dp in raw_data11:
data11.append(self.app.calibration.correct11(dp))
else:
data11 = raw_data11.copy()
if self.app.calibration.isValid2Port(): if self.app.calibration.isValid2Port():
for counter, dp in enumerate(raw_data21): for counter, dp in enumerate(raw_data21):

Wyświetl plik

@ -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

Wyświetl plik

@ -13,7 +13,7 @@ packages = find_namespace:
install_requires= install_requires=
pyserial>=3.5 pyserial>=3.5
PyQt5>=5.15.0 PyQt5>=5.15.0
numpy>=1.21.1,<1.22.0 numpy>=1.21.1
scipy>=1.7.1 scipy>=1.7.1
Cython>=0.29.24 Cython>=0.29.24
python_requires = >=3.8, <4 python_requires = >=3.8, <4