diff --git a/.gitignore b/.gitignore index 77b21b3..998ca08 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ settings.json .gitignore .coverage -/nanovna-saver.exe.spec \ No newline at end of file +.flatpak-builder +/nanovna-saver.exe.spec diff --git a/NanoVNASaver/Analysis/AntennaAnalysis.py b/NanoVNASaver/Analysis/AntennaAnalysis.py index c69078d..bbc97de 100644 --- a/NanoVNASaver/Analysis/AntennaAnalysis.py +++ b/NanoVNASaver/Analysis/AntennaAnalysis.py @@ -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", diff --git a/NanoVNASaver/Charts/Chart.py b/NanoVNASaver/Charts/Chart.py index abb5758..a5cfb33 100644 --- a/NanoVNASaver/Charts/Chart.py +++ b/NanoVNASaver/Charts/Chart.py @@ -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()) diff --git a/NanoVNASaver/Charts/Frequency.py b/NanoVNASaver/Charts/Frequency.py index f961a2f..e9ae60b 100644 --- a/NanoVNASaver/Charts/Frequency.py +++ b/NanoVNASaver/Charts/Frequency.py @@ -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) diff --git a/NanoVNASaver/Charts/GroupDelay.py b/NanoVNASaver/Charts/GroupDelay.py index ac44a79..1db1a91 100644 --- a/NanoVNASaver/Charts/GroupDelay.py +++ b/NanoVNASaver/Charts/GroupDelay.py @@ -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): diff --git a/NanoVNASaver/Charts/Polar.py b/NanoVNASaver/Charts/Polar.py index 8db1319..d2fec6e 100644 --- a/NanoVNASaver/Charts/Polar.py +++ b/NanoVNASaver/Charts/Polar.py @@ -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 . -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() diff --git a/NanoVNASaver/Charts/Smith.py b/NanoVNASaver/Charts/Smith.py index 2309963..ff18c7d 100644 --- a/NanoVNASaver/Charts/Smith.py +++ b/NanoVNASaver/Charts/Smith.py @@ -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 . -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() diff --git a/NanoVNASaver/Charts/Square.py b/NanoVNASaver/Charts/Square.py index 83b96d1..acc7b0f 100644 --- a/NanoVNASaver/Charts/Square.py +++ b/NanoVNASaver/Charts/Square.py @@ -17,23 +17,81 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . 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) diff --git a/NanoVNASaver/Hardware/Hardware.py b/NanoVNASaver/Hardware/Hardware.py index 68a5d7b..4e4047e 100644 --- a/NanoVNASaver/Hardware/Hardware.py +++ b/NanoVNASaver/Hardware/Hardware.py @@ -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__) diff --git a/NanoVNASaver/Hardware/Serial.py b/NanoVNASaver/Hardware/Serial.py index 1600637..f38b1ef 100644 --- a/NanoVNASaver/Hardware/Serial.py +++ b/NanoVNASaver/Hardware/Serial.py @@ -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 diff --git a/NanoVNASaver/Hardware/TinySA.py b/NanoVNASaver/Hardware/TinySA.py index 19af3bb..5208ebf 100644 --- a/NanoVNASaver/Hardware/TinySA.py +++ b/NanoVNASaver/Hardware/TinySA.py @@ -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 diff --git a/NanoVNASaver/Marker/Delta.py b/NanoVNASaver/Marker/Delta.py index 1f06876..f13f9b1 100644 --- a/NanoVNASaver/Marker/Delta.py +++ b/NanoVNASaver/Marker/Delta.py @@ -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)) diff --git a/NanoVNASaver/Marker/Values.py b/NanoVNASaver/Marker/Values.py index ee58d07..34ef586 100644 --- a/NanoVNASaver/Marker/Values.py +++ b/NanoVNASaver/Marker/Values.py @@ -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: diff --git a/NanoVNASaver/Marker/Widget.py b/NanoVNASaver/Marker/Widget.py index cf19aa1..9d47f32 100644 --- a/NanoVNASaver/Marker/Widget.py +++ b/NanoVNASaver/Marker/Widget.py @@ -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( diff --git a/NanoVNASaver/Settings/Bands.py b/NanoVNASaver/Settings/Bands.py index 3c6844d..87fb292 100644 --- a/NanoVNASaver/Settings/Bands.py +++ b/NanoVNASaver/Settings/Bands.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +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: diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index f01f7ce..c4da6d7 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -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): diff --git a/flatpak.manifest.yml b/flatpak.manifest.yml new file mode 100644 index 0000000..280fcfa --- /dev/null +++ b/flatpak.manifest.yml @@ -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 diff --git a/setup.cfg b/setup.cfg index dbeb32d..6ab3140 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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