From e03f951d83c9dd4e1bab1139e37b72ecd6f17a3b Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Mon, 30 Sep 2019 13:46:15 +0200 Subject: [PATCH 01/17] - Calibration saving improved - Notes for calibrations - Added load capacitance and delay, and through delay - Load capacitance is not yet fully implemented! --- NanoVNASaver/Calibration.py | 116 ++++++++++++++++++++++++------------ NanoVNASaver/Touchstone.py | 3 +- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 1d85899b..4ba264f7 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -38,7 +38,7 @@ def __init__(self, app): self.app: NanoVNASaver = app - self.setMinimumSize(600, 320) + self.setMinimumSize(450, 600) self.setWindowTitle("Calibration") shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -57,18 +57,6 @@ def __init__(self, app): calibration_status_group.setLayout(calibration_status_layout) left_layout.addWidget(calibration_status_group) - calibration_instructions_group = QtWidgets.QGroupBox("Instructions") - calibration_instructions_layout = QtWidgets.QVBoxLayout(calibration_instructions_group) - calibration_instructions_layout.addWidget(QtWidgets.QLabel("Instructions for use")) - instructions = QtWidgets.QLabel("For each calibration standard, first sweep in the main application window, " + - "then press the relevant button in this window. Short, open and load are " + - "sufficient for 1-port calibration. Sweep all standards with the same sweep " + - "count.") - instructions.setWordWrap(True) - calibration_instructions_layout.addWidget(instructions) - - left_layout.addWidget(calibration_instructions_group) - calibration_control_group = QtWidgets.QGroupBox("Calibrate") calibration_control_layout = QtWidgets.QFormLayout(calibration_control_group) btn_cal_short = QtWidgets.QPushButton("Short") @@ -115,15 +103,20 @@ def __init__(self, app): left_layout.addWidget(calibration_control_group) - file_box = QtWidgets.QGroupBox() + calibration_notes_group = QtWidgets.QGroupBox("Notes") + calibration_notes_layout = QtWidgets.QVBoxLayout(calibration_notes_group) + self.notes_textedit = QtWidgets.QPlainTextEdit() + calibration_notes_layout.addWidget(self.notes_textedit) + + left_layout.addWidget(calibration_notes_group) + + file_box = QtWidgets.QGroupBox("Files") file_layout = QtWidgets.QFormLayout(file_box) - filename_input = QtWidgets.QLineEdit(self.app.settings.value("CalibrationFile", "")) - file_layout.addRow("Filename", filename_input) btn_save_file = QtWidgets.QPushButton("Save calibration") - btn_save_file.clicked.connect(lambda: self.saveCalibration(filename_input.text())) + btn_save_file.clicked.connect(lambda: self.saveCalibration()) file_layout.addRow(btn_save_file) btn_load_file = QtWidgets.QPushButton("Load calibration") - btn_load_file.clicked.connect(lambda: self.loadFile(filename_input.text())) + btn_load_file.clicked.connect(lambda: self.loadCalibration()) file_layout.addRow(btn_load_file) left_layout.addWidget(file_box) @@ -168,12 +161,24 @@ def __init__(self, app): self.cal_load_box.setDisabled(True) self.load_resistance = QtWidgets.QLineEdit("50") self.load_inductance = QtWidgets.QLineEdit("0") + self.load_capacitance = QtWidgets.QLineEdit("0") + self.load_capacitance.setDisabled(True) # Not yet implemented + self.load_length = QtWidgets.QLineEdit("0") cal_load_form.addRow("Resistance (\N{OHM SIGN})", self.load_resistance) - cal_load_form.addRow("Inductance (H(e-12)", self.load_inductance) - + cal_load_form.addRow("Inductance (H(e-12))", self.load_inductance) + cal_load_form.addRow("Capacitance (F(e-12))", self.load_capacitance) + cal_load_form.addRow("Delay (ps)", self.load_length) + + self.cal_through_box = QtWidgets.QGroupBox("Through") + cal_through_form = QtWidgets.QFormLayout(self.cal_through_box) + self.cal_through_box.setDisabled(True) + self.through_length = QtWidgets.QLineEdit("0") + cal_through_form.addRow("Delay (ps)", self.through_length) + cal_standard_layout.addWidget(self.cal_short_box) cal_standard_layout.addWidget(self.cal_open_box) cal_standard_layout.addWidget(self.cal_load_box) + cal_standard_layout.addWidget(self.cal_through_box) right_layout.addWidget(cal_standard_box) def saveShort(self): @@ -204,6 +209,7 @@ def reset(self): self.cal_through_label.setText("Uncalibrated") self.cal_isolation_label.setText("Uncalibrated") self.calibration_status_label.setText("Device calibration") + self.notes_textedit.clear() def calculate(self): # TODO: Error handling for all the fields. @@ -229,31 +235,51 @@ def calculate(self): self.app.calibration.loadR = float(self.load_resistance.text()) self.app.calibration.loadL = float(self.load_inductance.text())/10**12 + self.app.calibration.loadC = float(self.load_capacitance.text()) / 10 ** 12 + self.app.calibration.loadLength = float(self.load_length.text())/10**12 self.app.calibration.useIdealLoad = False + self.app.calibration.throughLength = float(self.through_length.text())/10**12 + self.app.calibration.useIdealThrough = False + if self.app.calibration.calculateCorrections(): self.calibration_status_label.setText("Application calibration (" + str(len(self.app.calibration.s11short)) + " points)") - def loadFile(self, filename): - self.app.calibration.loadCalibration(filename) - if self.app.calibration.isValid1Port(): - self.cal_short_label.setText("Loaded (" + str(len(self.app.calibration.s11short)) + ")") - self.cal_open_label.setText("Loaded (" + str(len(self.app.calibration.s11open)) + ")") - self.cal_load_label.setText("Loaded (" + str(len(self.app.calibration.s11load)) + ")") - if self.app.calibration.isValid2Port(): - self.cal_through_label.setText("Loaded (" + str(len(self.app.calibration.s21through)) + ")") - self.cal_isolation_label.setText("Loaded (" + str(len(self.app.calibration.s21isolation)) + ")") - self.calculate() - self.app.settings.setValue("CalibrationFile", filename) - - def saveCalibration(self, filename): - if self.app.calibration.saveCalibration(filename): + def loadCalibration(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName(filter="Calibration Files (*.cal);;All files (*.*)") + if filename: + self.app.calibration.loadCalibration(filename) + if self.app.calibration.isValid1Port(): + self.cal_short_label.setText("Loaded (" + str(len(self.app.calibration.s11short)) + ")") + self.cal_open_label.setText("Loaded (" + str(len(self.app.calibration.s11open)) + ")") + self.cal_load_label.setText("Loaded (" + str(len(self.app.calibration.s11load)) + ")") + if self.app.calibration.isValid2Port(): + self.cal_through_label.setText("Loaded (" + str(len(self.app.calibration.s21through)) + ")") + self.cal_isolation_label.setText("Loaded (" + str(len(self.app.calibration.s21isolation)) + ")") + self.calculate() + self.notes_textedit.clear() + for note in self.app.calibration.notes: + self.notes_textedit.appendPlainText(note) + self.app.settings.setValue("CalibrationFile", filename) + + def saveCalibration(self): + if not self.app.calibration.isCalculated: + logger.debug("Attempted to save an uncalculated calibration.") + self.app.showError("Cannot save an unapplied calibration state.") + return + filename, _ = QtWidgets.QFileDialog.getSaveFileName(filter="Calibration Files (*.cal);;All files (*.*)") + self.app.calibration.notes = self.notes_textedit.toPlainText().splitlines() + if filename and self.app.calibration.saveCalibration(filename): self.app.settings.setValue("CalibrationFile", filename) + else: + logger.error("Calibration save failed!") + self.app.showError("Calibration save failed.") def idealCheckboxChanged(self): self.cal_short_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_open_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_load_box.setDisabled(self.use_ideal_values.isChecked()) + self.cal_through_box.setDisabled(self.use_ideal_values.isChecked()) def automaticCalibration(self): self.btn_automatic.setDisabled(True) @@ -433,6 +459,7 @@ def automaticCalibrationStep(self): class Calibration: + notes = [] s11short: List[Datapoint] = [] s11open: List[Datapoint] = [] s11load: List[Datapoint] = [] @@ -470,8 +497,13 @@ class Calibration: useIdealLoad = True loadR = 25 loadL = 0 + loadC = 0 + loadLength = 0 loadIdeal = np.complex(0, 0) + useIdealThrough = True + throughLength = 0 + isCalculated = False def isValid2Port(self): @@ -522,6 +554,7 @@ def calculateCorrections(self): else: Zl = self.loadR + 2 * math.pi * f * self.loadL g3 = ((Zl/50)-1) / ((Zl/50)+1) + g3 = g3 * np.exp(np.complex(0, 1) * 2 * 2 * math.pi * f * self.loadLength * -1) gm1 = np.complex(self.s11short[i].re, self.s11short[i].im) gm2 = np.complex(self.s11open[i].re, self.s11open[i].im) @@ -540,6 +573,9 @@ def calculateCorrections(self): if self.isValid2Port(): self.e30[i] = np.complex(self.s21isolation[i].re, self.s21isolation[i].im) s21m = np.complex(self.s21through[i].re, self.s21through[i].im) + if not self.useIdealThrough: + gammaThrough = math.exp(np.complex(0, 1) * 2 * 2 * math.pi * self.throughLength * f * -1) + s21m = s21m / gammaThrough self.e10e32[i] = (s21m - self.e30[i]) * (1 - (self.e11[i]*self.e11[i])) self.isCalculated = True @@ -576,6 +612,8 @@ def saveCalibration(self, filename): try: file = open(filename, "w+") file.write("# Calibration data for NanoVNA-Saver\n") + for note in self.notes: + file.write("! " + note + "\n") file.write("# Hz ShortR ShortI OpenR OpenI LoadR LoadI ThroughR ThroughI IsolationR IsolationI\n") for i in range(len(self.s11short)): freq = str(self.s11short[i].freq) @@ -605,19 +643,23 @@ def loadCalibration(self, filename): return self.s11short = [] - self.s11open = [] - self.s11load = [] + self.s11open = [] + self.s11load = [] - self.s21through = [] + self.s21through = [] self.s21isolation = [] + self.notes = [] try: file = open(filename, "r") lines = file.readlines() parsed_header = False + for l in lines: l = l.strip() if l.startswith("!"): + note = l[2:] + self.notes.append(note) continue if l.startswith("#") and not parsed_header: # Check that this is a valid header diff --git a/NanoVNASaver/Touchstone.py b/NanoVNASaver/Touchstone.py index cc98a7db..d3e1512f 100644 --- a/NanoVNASaver/Touchstone.py +++ b/NanoVNASaver/Touchstone.py @@ -27,7 +27,7 @@ class Touchstone: s11data: List[Datapoint] = [] s21data: List[Datapoint] = [] - + comments = [] filename = "" def __init__(self, filename): @@ -51,6 +51,7 @@ def load(self): line = line.strip() if line.startswith("!"): logger.info(line) + self.comments.append(line) continue if line.startswith("#") and not parsed_header: pattern = "^# (.?HZ) (S )?RI( R 50)?$" From d733a384b13b0efb069d1cef0fe74fe818c57652 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Mon, 30 Sep 2019 13:46:58 +0200 Subject: [PATCH 02/17] - 0.1.0alpha --- NanoVNASaver/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index a25c5321..aed557b2 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -14,5 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -version = '0.0.12' +version = '0.1.0alpha' From 06818260047d14213c5bbd391fbb5bb10a8ebb1a Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Mon, 30 Sep 2019 22:20:35 +0200 Subject: [PATCH 03/17] - First attempt at saving calibration standard sets. Delete doesn't work --- NanoVNASaver/Calibration.py | 149 ++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 4ba264f7..f12438a4 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -179,6 +179,28 @@ def __init__(self, app): cal_standard_layout.addWidget(self.cal_open_box) cal_standard_layout.addWidget(self.cal_load_box) cal_standard_layout.addWidget(self.cal_through_box) + + self.cal_standard_save_box = QtWidgets.QGroupBox("Saved settings") + cal_standard_save_layout = QtWidgets.QVBoxLayout(self.cal_standard_save_box) + self.cal_standard_save_box.setDisabled(True) + + self.cal_standard_save_selector = QtWidgets.QComboBox() + self.listCalibrationStandards() + cal_standard_save_layout.addWidget(self.cal_standard_save_selector) + cal_standard_save_button_layout = QtWidgets.QHBoxLayout() + btn_save_standard = QtWidgets.QPushButton("Save") + btn_save_standard.clicked.connect(self.saveCalibrationStandard) + btn_load_standard = QtWidgets.QPushButton("Load") + btn_load_standard.clicked.connect(self.loadCalibrationStandard) + btn_delete_standard = QtWidgets.QPushButton("Delete") + btn_delete_standard.clicked.connect(self.deleteCalibrationStandard) + btn_delete_standard.setDisabled(True) + cal_standard_save_button_layout.addWidget(btn_load_standard) + cal_standard_save_button_layout.addWidget(btn_save_standard) + cal_standard_save_button_layout.addWidget(btn_delete_standard) + cal_standard_save_layout.addLayout(cal_standard_save_button_layout) + + cal_standard_layout.addWidget(self.cal_standard_save_box) right_layout.addWidget(cal_standard_box) def saveShort(self): @@ -201,6 +223,132 @@ def saveThrough(self): self.app.calibration.s21through = self.app.data21 self.cal_through_label.setText("Calibrated (" + str(len(self.app.calibration.s21through)) + " points)") + def listCalibrationStandards(self): + self.cal_standard_save_selector.clear() + num_standards = self.app.settings.beginReadArray("CalibrationStandards") + for i in range(num_standards): + self.app.settings.setArrayIndex(i) + name = self.app.settings.value("Name", defaultValue="INVALID NAME") + self.cal_standard_save_selector.addItem(name, userData=i) + self.app.settings.endArray() + self.cal_standard_save_selector.addItem("New", userData=-1) + self.cal_standard_save_selector.setCurrentText("New") + + def saveCalibrationStandard(self): + if self.cal_standard_save_selector.currentData() == -1: + # New cal standard + # Get a name + name, selected = QtWidgets.QInputDialog.getText(self, "Calibration standard name", "Enter name to save as") + if not selected or not name: + return + num_standards = self.app.settings.beginReadArray("CalibrationStandards") + logger.debug("Number of standards known: %d", num_standards) + self.app.settings.endArray() + + if self.cal_standard_save_selector.currentData() == -1: + write_num = num_standards + num_standards += 1 + else: + write_num = self.cal_standard_save_selector.currentData() + name = self.cal_standard_save_selector.currentText() + + self.app.settings.beginWriteArray("CalibrationStandards", num_standards) + self.app.settings.setArrayIndex(write_num) + self.app.settings.setValue("Name", name) + + self.app.settings.setValue("ShortL0", self.short_l0_input.text()) + self.app.settings.setValue("ShortL1", self.short_l1_input.text()) + self.app.settings.setValue("ShortL2", self.short_l2_input.text()) + self.app.settings.setValue("ShortL3", self.short_l3_input.text()) + self.app.settings.setValue("ShortDelay", self.short_length.text()) + + self.app.settings.setValue("OpenC0", self.open_c0_input.text()) + self.app.settings.setValue("OpenC1", self.open_c1_input.text()) + self.app.settings.setValue("OpenC2", self.open_c2_input.text()) + self.app.settings.setValue("OpenC3", self.open_c3_input.text()) + self.app.settings.setValue("OpenDelay", self.open_length.text()) + + self.app.settings.setValue("LoadR", self.load_resistance.text()) + self.app.settings.setValue("LoadL", self.load_inductance.text()) + self.app.settings.setValue("LoadC", self.load_capacitance.text()) + self.app.settings.setValue("LoadDelay", self.load_length.text()) + + self.app.settings.setValue("ThroughDelay", self.through_length.text()) + + self.app.settings.endArray() + self.app.settings.sync() + self.listCalibrationStandards() + self.cal_standard_save_selector.setCurrentText(name) + + def loadCalibrationStandard(self): + if self.cal_standard_save_selector.currentData() == -1: + return + read_num = self.cal_standard_save_selector.currentData() + logger.debug("Loading calibration no %d", read_num) + self.app.settings.beginReadArray("CalibrationStandards") + self.app.settings.setArrayIndex(read_num) + + name = self.app.settings.value("Name") + logger.info("Loading: %s", name) + + self.short_l0_input.setText(str(self.app.settings.value("ShortL0", 0))) + self.short_l1_input.setText(str(self.app.settings.value("ShortL1", 0))) + self.short_l2_input.setText(str(self.app.settings.value("ShortL2", 0))) + self.short_l3_input.setText(str(self.app.settings.value("ShortL3", 0))) + self.short_length.setText(str(self.app.settings.value("ShortDelay", 0))) + + self.open_c0_input.setText(str(self.app.settings.value("OpenC0", 50))) + self.open_c1_input.setText(str(self.app.settings.value("OpenC1", 0))) + self.open_c2_input.setText(str(self.app.settings.value("OpenC2", 0))) + self.open_c3_input.setText(str(self.app.settings.value("OpenC3", 0))) + self.open_length.setText(str(self.app.settings.value("OpenDelay", 0))) + + self.load_resistance.setText(str(self.app.settings.value("LoadR", 50))) + self.load_inductance.setText(str(self.app.settings.value("LoadL", 0))) + self.load_capacitance.setText(str(self.app.settings.value("LoadC", 0))) + self.load_length.setText(str(self.app.settings.value("LoadDelay", 0))) + + self.through_length.setText(str(self.app.settings.value("ThroughDelay", 0))) + + self.app.settings.endArray() + + def deleteCalibrationStandard(self): + # TODO: This does not currently work. We need to re-write all the settings in the array so they get renumbered. + if self.cal_standard_save_selector.currentData() == -1: + return + write_num = self.cal_standard_save_selector.currentData() + logger.debug("Deleting calibration no %d", write_num) + num_standards = self.app.settings.beginReadArray("CalibrationStandards") + logger.debug("Number of standards known: %d", num_standards) + self.app.settings.endArray() + + self.app.settings.beginWriteArray("CalibrationStandards", num_standards-1) + self.app.settings.setArrayIndex(write_num) + self.app.settings.remove("Name") + + self.app.settings.remove("ShortL0") + self.app.settings.remove("ShortL1") + self.app.settings.remove("ShortL2") + self.app.settings.remove("ShortL3") + self.app.settings.remove("ShortDelay") + + self.app.settings.remove("OpenC0") + self.app.settings.remove("OpenC1") + self.app.settings.remove("OpenC2") + self.app.settings.remove("OpenC3") + self.app.settings.remove("OpenDelay") + + self.app.settings.remove("LoadR") + self.app.settings.remove("LoadL") + self.app.settings.remove("LoadC") + self.app.settings.remove("LoadDelay") + + self.app.settings.remove("ThroughDelay") + + self.app.settings.endArray() + self.app.settings.sync() + self.listCalibrationStandards() + def reset(self): self.app.calibration = Calibration() self.cal_short_label.setText("Uncalibrated") @@ -280,6 +428,7 @@ def idealCheckboxChanged(self): self.cal_open_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_load_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_through_box.setDisabled(self.use_ideal_values.isChecked()) + self.cal_standard_save_box.setDisabled(self.use_ideal_values.isChecked()) def automaticCalibration(self): self.btn_automatic.setDisabled(True) From 40fd3aeaf6041e784a9afc075f34567a6b025a79 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 09:27:58 +0200 Subject: [PATCH 04/17] - Calibration bugfixes - Use margins properly in charts, part 1 --- NanoVNASaver/Calibration.py | 4 +-- NanoVNASaver/Chart.py | 51 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index f12438a4..cfc83c6a 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -701,7 +701,7 @@ def calculateCorrections(self): if self.useIdealLoad: g3 = self.loadIdeal else: - Zl = self.loadR + 2 * math.pi * f * self.loadL + Zl = self.loadR + (np.complex(0, 1) * 2 * math.pi * f * self.loadL) g3 = ((Zl/50)-1) / ((Zl/50)+1) g3 = g3 * np.exp(np.complex(0, 1) * 2 * 2 * math.pi * f * self.loadLength * -1) @@ -723,7 +723,7 @@ def calculateCorrections(self): self.e30[i] = np.complex(self.s21isolation[i].re, self.s21isolation[i].im) s21m = np.complex(self.s21through[i].re, self.s21through[i].im) if not self.useIdealThrough: - gammaThrough = math.exp(np.complex(0, 1) * 2 * 2 * math.pi * self.throughLength * f * -1) + gammaThrough = np.exp(np.complex(0, 1) * 2 * 2 * math.pi * self.throughLength * f * -1) s21m = s21m / gammaThrough self.e10e32[i] = (s21m - self.e30[i]) * (1 - (self.e11[i]*self.e11[i])) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index c7b48204..10defe1b 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -360,15 +360,15 @@ def drawBands(self, qp, fstart, fstop): # The band is entirely within the chart x_start = self.getXPosition(Datapoint(start, 0, 0)) x_end = self.getXPosition(Datapoint(end, 0, 0)) - qp.drawRect(x_start, 30, x_end - x_start, self.chartHeight - 10) + qp.drawRect(x_start, self.topMargin, x_end - x_start, self.chartHeight) elif fstart < start < fstop: # Only the start of the band is within the chart x_start = self.getXPosition(Datapoint(start, 0, 0)) - qp.drawRect(x_start, 30, self.leftMargin + self.chartWidth - x_start, self.chartHeight - 10) + qp.drawRect(x_start, self.topMargin, self.leftMargin + self.chartWidth - x_start, self.chartHeight) elif fstart < end < fstop: # Only the end of the band is within the chart x_end = self.getXPosition(Datapoint(end, 0, 0)) - qp.drawRect(self.leftMargin + 1, 30, x_end - (self.leftMargin + 1), self.chartHeight - 10) + qp.drawRect(self.leftMargin + 1, self.topMargin, x_end - (self.leftMargin + 1), self.chartHeight) elif start < fstart < fstop < end: # All the chart is in a band, we won't show it(?) pass @@ -417,9 +417,11 @@ def getPlotable(self, x, y, distantx, distanty): p1 = np.array([x, y]) p2 = np.array([distantx, distanty]) # First check the top line - p3 = np.array([self.leftMargin, self.topMargin]) - p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin]) - + if distanty < self.topMargin: + p3 = np.array([self.leftMargin, self.topMargin]) + p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin]) + else: + return x, y da = p2 - p1 db = p4 - p3 dp = p1 - p3 @@ -461,7 +463,7 @@ def __init__(self, name=""): self.minDisplayValue = -180 self.maxDisplayValue = 180 - self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.lowerMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) @@ -472,8 +474,8 @@ def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, 15, self.name) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth, 20 + self.chartHeight) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) if self.fixedValues: minAngle = self.minDisplayValue maxAngle = self.maxDisplayValue @@ -486,17 +488,16 @@ def drawChart(self, qp: QtGui.QPainter): self.span = span step = math.floor(span/4) for i in range(minAngle, maxAngle, step): - y = 30 + round((maxAngle - i)/span*(self.chartHeight-10)) - logger.debug("Plotting %d at y = %d", i, y) + y = self.topMargin + round((maxAngle - i)/span*self.chartHeight) if i != minAngle and i != maxAngle and (maxAngle - i) > step / 2: qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, y+3, str(i) + "°") qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) - qp.drawLine(self.leftMargin - 5, 30, self.leftMargin + self.chartWidth, 30) + qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin) qp.setPen(self.textColor) qp.drawText(3, 35, str(maxAngle) + "°") - qp.drawText(3, self.chartHeight+20, str(minAngle) + "°") + qp.drawText(3, self.chartHeight+self.topMargin, str(minAngle) + "°") def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: @@ -525,14 +526,14 @@ def drawValues(self, qp: QtGui.QPainter): self.drawBands(qp, fstart, fstop) qp.setPen(self.textColor) - qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) + qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin for i in range(ticks): x = self.leftMargin + round((i+1)*self.chartWidth/ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(x, 20, x, 20+self.chartHeight+5) qp.setPen(self.textColor) - qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) + qp.drawText(x-20, self.topMargin+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -540,7 +541,7 @@ def drawValues(self, qp: QtGui.QPainter): def getYPosition(self, d: Datapoint) -> int: angle = self.angle(d) - return 30 + round((self.maxAngle - angle) / self.span * (self.chartHeight - 10)) + return 30 + round((self.maxAngle - angle) / self.span * self.chartHeight) @staticmethod def angle(d: Datapoint) -> float: @@ -943,7 +944,7 @@ def __init__(self, name=""): self.minDisplayValue = -80 self.maxDisplayValue = 10 - self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.lowerMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) @@ -960,8 +961,8 @@ def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, 15, self.name + " (dB)") qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth, 20 + self.chartHeight) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: @@ -1022,7 +1023,7 @@ def drawValues(self, qp: QtGui.QPainter): span = maxValue-minValue self.span = span for i in range(minValue, maxValue, 10): - y = 30 + round((i-minValue)/span*(self.chartHeight-10)) + y = self.topMargin + round((i-minValue)/span*(self.chartHeight-10)) qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) if i > minValue: @@ -1030,17 +1031,17 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawText(3, y + 4, str(-i)) qp.setPen(self.textColor) qp.drawText(3, 35, str(-minValue)) - qp.drawText(3, self.chartHeight+20, str(-maxValue)) + qp.drawText(3, self.chartHeight+self.topMargin, str(-maxValue)) # Frequency ticks - qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) + qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin for i in range(ticks): x = self.leftMargin + round((i+1)*self.chartWidth/ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, 20+self.chartHeight+5) + qp.drawLine(x, 20, x, self.topMargin+self.chartHeight+5) qp.setPen(self.textColor) - qp.drawText(x-20, 20+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) + qp.drawText(x-20, self.topMargin+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -1048,7 +1049,7 @@ def drawValues(self, qp: QtGui.QPainter): def getYPosition(self, d: Datapoint) -> int: logMag = self.logMag(d) - return 30 + round((logMag - self.minValue) / self.span * (self.chartHeight - 10)) + return self.topMargin + round((logMag - self.minValue) / self.span * self.chartHeight) @staticmethod def logMag(p: Datapoint) -> float: From bf28eb6846310dd644d11cb4ada8fe185e0c0d70 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 11:19:26 +0200 Subject: [PATCH 05/17] - Label delay correctly as "Offset Delay" --- NanoVNASaver/Calibration.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index cfc83c6a..efd1fc6c 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -140,7 +140,7 @@ def __init__(self, app): cal_short_form.addRow("L1 (F(e-24))", self.short_l1_input) cal_short_form.addRow("L2 (F(e-33))", self.short_l2_input) cal_short_form.addRow("L3 (F(e-42))", self.short_l3_input) - cal_short_form.addRow("Delay (ps)", self.short_length) + cal_short_form.addRow("Offset Delay (ps)", self.short_length) self.cal_open_box = QtWidgets.QGroupBox("Open") cal_open_form = QtWidgets.QFormLayout(self.cal_open_box) @@ -154,7 +154,7 @@ def __init__(self, app): cal_open_form.addRow("C1 (H(e-27))", self.open_c1_input) cal_open_form.addRow("C2 (H(e-36))", self.open_c2_input) cal_open_form.addRow("C3 (H(e-45))", self.open_c3_input) - cal_open_form.addRow("Delay (ps)", self.open_length) + cal_open_form.addRow("Offset Delay (ps)", self.open_length) self.cal_load_box = QtWidgets.QGroupBox("Load") cal_load_form = QtWidgets.QFormLayout(self.cal_load_box) @@ -167,13 +167,13 @@ def __init__(self, app): cal_load_form.addRow("Resistance (\N{OHM SIGN})", self.load_resistance) cal_load_form.addRow("Inductance (H(e-12))", self.load_inductance) cal_load_form.addRow("Capacitance (F(e-12))", self.load_capacitance) - cal_load_form.addRow("Delay (ps)", self.load_length) + cal_load_form.addRow("Offset Delay (ps)", self.load_length) self.cal_through_box = QtWidgets.QGroupBox("Through") cal_through_form = QtWidgets.QFormLayout(self.cal_through_box) self.cal_through_box.setDisabled(True) self.through_length = QtWidgets.QLineEdit("0") - cal_through_form.addRow("Delay (ps)", self.through_length) + cal_through_form.addRow("Offset Delay (ps)", self.through_length) cal_standard_layout.addWidget(self.cal_short_box) cal_standard_layout.addWidget(self.cal_open_box) @@ -235,17 +235,15 @@ def listCalibrationStandards(self): self.cal_standard_save_selector.setCurrentText("New") def saveCalibrationStandard(self): + num_standards = self.app.settings.beginReadArray("CalibrationStandards") + self.app.settings.endArray() + if self.cal_standard_save_selector.currentData() == -1: # New cal standard # Get a name name, selected = QtWidgets.QInputDialog.getText(self, "Calibration standard name", "Enter name to save as") if not selected or not name: return - num_standards = self.app.settings.beginReadArray("CalibrationStandards") - logger.debug("Number of standards known: %d", num_standards) - self.app.settings.endArray() - - if self.cal_standard_save_selector.currentData() == -1: write_num = num_standards num_standards += 1 else: From b9f9cd07ad49cc0091c4b9949bdd60fa9de13ea2 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 13:00:59 +0200 Subject: [PATCH 06/17] - Use margins properly. --- NanoVNASaver/Chart.py | 189 +++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 76 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 10defe1b..2f04c2c7 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -184,7 +184,7 @@ class FrequencyChart(Chart): leftMargin = 30 rightMargin = 20 - lowerMargin = 20 + bottomMargin = 20 topMargin = 30 def __init__(self, name): @@ -343,7 +343,7 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: self.chartWidth = a0.size().width()-self.rightMargin-self.leftMargin - self.chartHeight = a0.size().height()-self.lowerMargin-self.topMargin + self.chartHeight = a0.size().height() - self.bottomMargin - self.topMargin self.update() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: @@ -463,7 +463,7 @@ def __init__(self, name=""): self.minDisplayValue = -180 self.maxDisplayValue = 180 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.lowerMargin) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) @@ -561,8 +561,10 @@ def __init__(self, name=""): self.maxDisplayValue = 25 self.minDisplayValue = 1 - self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, + self.chartHeight + self.topMargin + self.bottomMargin) + self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) self.setPalette(pal) @@ -572,8 +574,10 @@ def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, 15, self.name) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth, 20 + self.chartHeight) + qp.drawLine(self.leftMargin, self.topMargin - 5, + self.leftMargin, self.topMargin + self.chartHeight + 5) + qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight, + self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) def drawValues(self, qp: QtGui.QPainter): from NanoVNASaver.NanoVNASaver import NanoVNASaver @@ -630,26 +634,26 @@ def drawValues(self, qp: QtGui.QPainter): ticksize = 2 for i in range(minVSWR, maxVSWR, ticksize): - y = 30 + round((maxVSWR-i)/span*(self.chartHeight-10)) + y = self.topMargin + round((maxVSWR-i)/span*self.chartHeight) if i != minVSWR and i != maxVSWR: qp.setPen(self.textColor) qp.drawText(3, y+3, str(i)) qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) - qp.drawLine(self.leftMargin - 5, 30, self.leftMargin + self.chartWidth, 30) + qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin) qp.setPen(self.textColor) qp.drawText(3, 35, str(maxVSWR)) - qp.drawText(3, self.chartHeight+20, str(minVSWR)) + qp.drawText(3, self.chartHeight + self.topMargin, str(minVSWR)) # At least 100 px between ticks - qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(fstart)) + qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin for i in range(ticks): x = self.leftMargin + round((i+1)*self.chartWidth/ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, 20+self.chartHeight+5) + qp.drawLine(x, self.topMargin, x, self.topMargin + self.chartHeight + 5) qp.setPen(self.textColor) - qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) + qp.drawText(x-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -658,7 +662,7 @@ def drawValues(self, qp: QtGui.QPainter): def getYPosition(self, d: Datapoint) -> int: from NanoVNASaver.NanoVNASaver import NanoVNASaver _, _, vswr = NanoVNASaver.vswr(d) - return 30 + round((self.maxVSWR - vswr) / self.span * (self.chartHeight - 10)) + return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.chartHeight) def resetDisplayLimits(self): self.maxDisplayValue = 25 @@ -944,7 +948,7 @@ def __init__(self, name=""): self.minDisplayValue = -80 self.maxDisplayValue = 10 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.lowerMargin) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) @@ -1076,8 +1080,10 @@ def __init__(self, name=""): self.minDisplayValue = 0 self.maxDisplayValue = 100 - self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) + self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, + self.chartHeight + self.topMargin + self.bottomMargin) + self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) self.setPalette(pal) @@ -1088,8 +1094,9 @@ def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, 15, self.name) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth, 20 + self.chartHeight) + qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5) + qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight, + self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) maxQ = 0 # Make up some sensible scaling here @@ -1115,12 +1122,12 @@ def drawChart(self, qp: QtGui.QPainter): if self.span == 0: return # No data to draw the graph from for i in range(self.minQ, self.maxQ, step): - y = 30 + round((self.maxQ - i) / self.span * (self.chartHeight-10)) + y = self.topMargin + round((self.maxQ - i) / self.span * self.chartHeight) qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, y+3, str(i)) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) - qp.drawLine(self.leftMargin - 5, 30, self.leftMargin + self.chartWidth, 30) + qp.drawLine(self.leftMargin-5, y, self.leftMargin + self.chartWidth, y) + qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin) qp.setPen(self.textColor) qp.drawText(3, 35, str(self.maxQ)) @@ -1155,14 +1162,15 @@ def drawValues(self, qp: QtGui.QPainter): self.drawBands(qp, fstart, fstop) qp.setPen(self.textColor) - qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(fstart)) + qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin for i in range(ticks): x = self.leftMargin + round((i+1)*self.chartWidth/ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, 20+self.chartHeight+5) + qp.drawLine(x, self.topMargin - 5, x, self.topMargin + self.chartHeight + 5) qp.setPen(self.textColor) - qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) + qp.drawText(x - 20, self.topMargin + self.chartHeight + 15, + Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -1171,7 +1179,7 @@ def drawValues(self, qp: QtGui.QPainter): def getYPosition(self, d: Datapoint) -> int: from NanoVNASaver.NanoVNASaver import NanoVNASaver Q = NanoVNASaver.qualifyFactor(d) - return 30 + round((self.maxQ - Q) / self.span * (self.chartHeight - 10)) + return self.topMargin + round((self.maxQ - Q) / self.span * self.chartHeight) class TDRChart(Chart): @@ -1180,7 +1188,7 @@ def __init__(self, name): self.tdrWindow = None self.leftMargin = 20 self.rightMargin = 20 - self.lowerMargin = 35 + self.bottomMargin = 35 self.setMinimumSize(250, 250) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() @@ -1194,12 +1202,12 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp.drawText(3, 15, self.name) width = self.width() - self.leftMargin - self.rightMargin - height = self.height() - self.lowerMargin + height = self.height() - self.bottomMargin qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, self.height() - self.lowerMargin, self.width() - self.rightMargin, - self.height() - self.lowerMargin) - qp.drawLine(self.leftMargin, 20, self.leftMargin, self.height() - self.lowerMargin + 5) + qp.drawLine(self.leftMargin - 5, self.height() - self.bottomMargin, self.width() - self.rightMargin, + self.height() - self.bottomMargin) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.height() - self.bottomMargin + 5) ticks = math.floor((self.width() - self.leftMargin)/100) # Number of ticks does not include the origin @@ -1302,8 +1310,9 @@ def drawChart(self, qp: QtGui.QPainter): qp.drawText(10, 15, "R") qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X") qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth+5, 20 + self.chartHeight) + qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5) + qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight, + self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight) def drawValues(self, qp: QtGui.QPainter): from NanoVNASaver.NanoVNASaver import NanoVNASaver @@ -1411,7 +1420,7 @@ def drawValues(self, qp: QtGui.QPainter): horizontal_ticks = math.floor(self.chartHeight/50) for i in range(horizontal_ticks): - y = 30 + round(i * (self.chartHeight-10) / horizontal_ticks) + y = self.topMargin + round(i * self.chartHeight / horizontal_ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y) qp.setPen(QtGui.QPen(self.textColor)) @@ -1420,17 +1429,17 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawText(3, y + 4, str(round(re, 1))) qp.drawText(self.leftMargin + self.chartWidth + 8, y + 4, str(round(im, 1))) - qp.drawText(3, self.chartHeight + 20, str(round(min_real, 1))) - qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + 20, str(round(min_imag, 1))) + qp.drawText(3, self.chartHeight + self.topMargin, str(round(min_real, 1))) + qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + self.topMargin, str(round(min_imag, 1))) - qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(fstart)) + qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin for i in range(ticks): x = self.leftMargin + round((i+1)*self.chartWidth/ticks) qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, 20+self.chartHeight+5) + qp.drawLine(x, self.topMargin - 5, x, self.topMargin + self.chartHeight + 5) qp.setPen(self.textColor) - qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) + qp.drawText(x-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) primary_pen = pen secondary_pen = QtGui.QPen(self.secondarySweepColor) @@ -1449,29 +1458,43 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9) for i in range(len(self.data)): - re, im = NanoVNASaver.normalize50(self.data[i]) x = self.getXPosition(self.data[i]) - y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10)) - y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10)) + y_re = self.getReYPosition(self.data[i]) + y_im = self.getImYPosition(self.data[i]) qp.setPen(primary_pen) - if re > 0: - qp.drawPoint(int(x), int(y_re)) + if self.isPlotable(x, y_re): + qp.drawPoint(x, y_re) qp.setPen(secondary_pen) - qp.drawPoint(int(x), int(y_im)) + if self.isPlotable(x, y_im): + qp.drawPoint(x, y_im) if self.drawLines and i > 0: - new_re, new_im = NanoVNASaver.normalize50(self.data[i-1]) - prev_x = self.getXPosition(self.data[i-1]) - prev_y_re = 30 + round((max_real - new_re) / span_real * (self.chartHeight - 10)) - prev_y_im = 30 + round((max_imag - new_im) / span_imag * (self.chartHeight - 10)) + prev_x = self.getXPosition(self.data[i - 1]) + prev_y_re = self.getReYPosition(self.data[i-1]) + prev_y_im = self.getImYPosition(self.data[i-1]) - if re > 0 and new_re > 0: + # Real part first + if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): line_pen.setColor(self.sweepColor) qp.setPen(line_pen) qp.drawLine(x, y_re, prev_x, prev_y_re) - - line_pen.setColor(self.secondarySweepColor) - qp.setPen(line_pen) - qp.drawLine(x, y_im, prev_x, prev_y_im) + elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re): + new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re) + qp.drawLine(x, y_re, new_x, new_y) + elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): + new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) + qp.drawLine(prev_x, prev_y_re, new_x, new_y) + + # Imag part first + if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): + line_pen.setColor(self.secondarySweepColor) + qp.setPen(line_pen) + qp.drawLine(x, y_im, prev_x, prev_y_im) + elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im): + new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im) + qp.drawLine(x, y_im, new_x, new_y) + elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): + new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) + qp.drawLine(prev_x, prev_y_im, new_x, new_y) primary_pen.setColor(self.referenceColor) line_pen.setColor(self.referenceColor) @@ -1489,38 +1512,52 @@ def drawValues(self, qp: QtGui.QPainter): for i in range(len(self.reference)): if self.reference[i].freq < fstart or self.reference[i].freq > fstop: continue - re, im = NanoVNASaver.normalize50(self.reference[i]) x = self.getXPosition(self.reference[i]) - y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10)) - y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10)) + y_re = self.getReYPosition(self.reference[i]) + y_im = self.getImYPosition(self.reference[i]) qp.setPen(primary_pen) - if re > 0: - qp.drawPoint(int(x), int(y_re)) + if self.isPlotable(x, y_re): + qp.drawPoint(x, y_re) qp.setPen(secondary_pen) - qp.drawPoint(int(x), int(y_im)) - + if self.isPlotable(x, y_im): + qp.drawPoint(x, y_im) if self.drawLines and i > 0: - new_re, new_im = NanoVNASaver.normalize50(self.reference[i-1]) - prev_x = self.getXPosition(self.reference[i-1]) - prev_y_re = 30 + round((max_real - new_re) / span_real * (self.chartHeight - 10)) - prev_y_im = 30 + round((max_imag - new_im) / span_imag * (self.chartHeight - 10)) + prev_x = self.getXPosition(self.reference[i - 1]) + prev_y_re = self.getReYPosition(self.reference[i-1]) + prev_y_im = self.getImYPosition(self.reference[i-1]) - if re > 0 and new_re > 0: - line_pen.setColor(self.referenceColor) + # Real part first + if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): + line_pen.setColor(self.sweepColor) qp.setPen(line_pen) qp.drawLine(x, y_re, prev_x, prev_y_re) - - qp.drawLine(x, y_im, prev_x, prev_y_im) + elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re): + new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re) + qp.drawLine(x, y_re, new_x, new_y) + elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): + new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) + qp.drawLine(prev_x, prev_y_re, new_x, new_y) + + # Imag part first + if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): + line_pen.setColor(self.secondarySweepColor) + qp.setPen(line_pen) + qp.drawLine(x, y_im, prev_x, prev_y_im) + elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im): + new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im) + qp.drawLine(x, y_im, new_x, new_y) + elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): + new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) + qp.drawLine(prev_x, prev_y_im, new_x, new_y) # Now draw the markers for m in self.markers: if m.location != -1: highlighter.setColor(m.color) qp.setPen(highlighter) - re, im = NanoVNASaver.normalize50(self.data[m.location]) x = self.getXPosition(self.data[m.location]) - y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10)) - y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10)) + y_re = self.getReYPosition(self.data[m.location]) + y_im = self.getImYPosition(self.data[m.location]) qp.drawLine(int(x), int(y_re) + 3, int(x) - 3, int(y_re) - 3) qp.drawLine(int(x), int(y_re) + 3, int(x) + 3, int(y_re) - 3) @@ -1532,13 +1569,13 @@ def drawValues(self, qp: QtGui.QPainter): def getImYPosition(self, d: Datapoint) -> int: from NanoVNASaver.NanoVNASaver import NanoVNASaver - re, im = NanoVNASaver.normalize50(d) - return 30 + round((self.max_imag - im) / self.span_imag * (self.chartHeight - 10)) + _, im = NanoVNASaver.normalize50(d) + return self.topMargin + round((self.max_imag - im) / self.span_imag * self.chartHeight) def getReYPosition(self, d: Datapoint) -> int: from NanoVNASaver.NanoVNASaver import NanoVNASaver - re, im = NanoVNASaver.normalize50(d) - return 30 + round((self.max_real - re) / self.span_real * (self.chartHeight - 10)) + re, _ = NanoVNASaver.normalize50(d) + return self.topMargin + round((self.max_real - re) / self.span_real * self.chartHeight) def getNearestMarker(self, x, y) -> Marker: if len(self.data) == 0: From 1397aef995547aded1715f4c6f99e016acd742fe Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 13:23:38 +0200 Subject: [PATCH 07/17] - Error handling for serial port problems --- NanoVNASaver/Calibration.py | 22 +++++++++++----------- NanoVNASaver/Chart.py | 1 - NanoVNASaver/NanoVNASaver.py | 20 +++++++++++++------- NanoVNASaver/SweepWorker.py | 6 ++++++ 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index efd1fc6c..2da9a979 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -802,33 +802,33 @@ def loadCalibration(self, filename): lines = file.readlines() parsed_header = False - for l in lines: - l = l.strip() - if l.startswith("!"): - note = l[2:] + for line in lines: + line = line.strip() + if line.startswith("!"): + note = line[2:] self.notes.append(note) continue - if l.startswith("#") and not parsed_header: + if line.startswith("#") and not parsed_header: # Check that this is a valid header - if l == "# Hz ShortR ShortI OpenR OpenI LoadR LoadI ThroughR ThroughI IsolationR IsolationI": + if line == "# Hz ShortR ShortI OpenR OpenI LoadR LoadI ThroughR ThroughI IsolationR IsolationI": parsed_header = True continue else: # This is some other comment line continue if not parsed_header: - logger.warning("Warning: Read line without having read header: %s", l) + logger.warning("Warning: Read line without having read header: %s", line) continue try: - if l.count(" ") == 6: - freq, shortr, shorti, openr, openi, loadr, loadi = l.split(" ") + if line.count(" ") == 6: + freq, shortr, shorti, openr, openi, loadr, loadi = line.split(" ") self.s11short.append(Datapoint(int(freq), float(shortr), float(shorti))) self.s11open.append(Datapoint(int(freq), float(openr), float(openi))) self.s11load.append(Datapoint(int(freq), float(loadr), float(loadi))) else: - freq, shortr, shorti, openr, openi, loadr, loadi, throughr, throughi, isolationr, isolationi = l.split(" ") + freq, shortr, shorti, openr, openi, loadr, loadi, throughr, throughi, isolationr, isolationi = line.split(" ") self.s11short.append(Datapoint(int(freq), float(shortr), float(shorti))) self.s11open.append(Datapoint(int(freq), float(openr), float(openi))) self.s11load.append(Datapoint(int(freq), float(loadr), float(loadi))) @@ -836,7 +836,7 @@ def loadCalibration(self, filename): self.s21isolation.append(Datapoint(int(freq), float(isolationr), float(isolationi))) except ValueError as e: - logger.exception("Error parsing calibration data \"%s\": %s", l, e) + logger.exception("Error parsing calibration data \"%s\": %s", line, e) file.close() except Exception as e: logger.exception("Failed loading calibration data: %s", e) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 2f04c2c7..0e45b5aa 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -1132,7 +1132,6 @@ def drawChart(self, qp: QtGui.QPainter): qp.drawText(3, 35, str(self.maxQ)) def drawValues(self, qp: QtGui.QPainter): - from NanoVNASaver.NanoVNASaver import NanoVNASaver if len(self.data) == 0 and len(self.reference) == 0: return if self.span == 0: diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 1d594a77..e85d470d 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -595,13 +595,17 @@ def startSerial(self): logger.info(self.readFirmware()) frequencies = self.readValues("frequencies") - logger.info("Read starting frequency %s and end frequency %s", frequencies[0], frequencies[100]) - if int(frequencies[0]) == int(frequencies[100]) and (self.sweepStartInput.text() == "" or self.sweepEndInput.text() == ""): - self.sweepStartInput.setText(frequencies[0]) - self.sweepEndInput.setText(str(int(frequencies[100]) + 100000)) - elif self.sweepStartInput.text() == "" or self.sweepEndInput.text() == "": - self.sweepStartInput.setText(frequencies[0]) - self.sweepEndInput.setText(frequencies[100]) + if frequencies: + logger.info("Read starting frequency %s and end frequency %s", frequencies[0], frequencies[100]) + if int(frequencies[0]) == int(frequencies[100]) and (self.sweepStartInput.text() == "" or self.sweepEndInput.text() == ""): + self.sweepStartInput.setText(frequencies[0]) + self.sweepEndInput.setText(str(int(frequencies[100]) + 100000)) + elif self.sweepStartInput.text() == "" or self.sweepEndInput.text() == "": + self.sweepStartInput.setText(frequencies[0]) + self.sweepEndInput.setText(frequencies[100]) + else: + logger.warning("No frequencies read") + return logger.debug("Starting initial sweep") self.sweep() @@ -696,6 +700,8 @@ def readValues(self, value): values = result.split("\r\n") except serial.SerialException as exc: logger.exception("Exception while reading %s: %s", value, exc) + self.serialLock.release() + return self.serialLock.release() return values[1:102] diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index 7a45c4f6..a05eae45 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -295,6 +295,9 @@ def readData(self, data): done = True returndata = [] tmpdata = self.app.readValues(data) + if not tmpdata: + logger.warning("Read no values") + raise NanoVNAValueException("Failed reading data: Returned no values.") logger.debug("Read %d values", len(tmpdata)) for d in tmpdata: a, b = d.split(" ") @@ -334,6 +337,9 @@ def readFreq(self): done = True returnfreq = [] tmpfreq = self.app.readValues("frequencies") + if not tmpfreq: + logger.warning("Read no frequencies") + raise NanoVNAValueException("Failed reading frequencies: Returned no values.") for f in tmpfreq: if not f.isdigit(): logger.warning("Got a non-digit frequency: %s", f) From 0d46ab633e9624be178190daea67c0069c18cbc3 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 14:19:07 +0200 Subject: [PATCH 08/17] - Handling of real and imaginary pure 0 values, for example from sims. --- NanoVNASaver/Chart.py | 9 ++------- NanoVNASaver/Marker.py | 33 ++++++++++++++++++++++++--------- NanoVNASaver/NanoVNASaver.py | 8 ++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 0e45b5aa..34213bf1 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -1057,13 +1057,8 @@ def getYPosition(self, d: Datapoint) -> int: @staticmethod def logMag(p: Datapoint) -> float: - re = p.re - im = p.im - re50 = 50 * (1 - re * re - im * im) / (1 + re * re + im * im - 2 * re) - im50 = 50 * (2 * im) / (1 + re * re + im * im - 2 * re) - # Calculate the reflection coefficient - mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / math.sqrt((re50 + 50) * (re50 + 50) + im50 * im50) - return -20 * math.log10(mag) + from NanoVNASaver.NanoVNASaver import NanoVNASaver + return NanoVNASaver.gain(p) class QualityFactorChart(FrequencyChart): diff --git a/NanoVNASaver/Marker.py b/NanoVNASaver/Marker.py index 68d1dc2c..9697fe6a 100644 --- a/NanoVNASaver/Marker.py +++ b/NanoVNASaver/Marker.py @@ -85,7 +85,6 @@ def __init__(self, name, initialColor, frequency=""): line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.VLine) - #line.setFrameShadow(QtWidgets.QFrame.Sunken) left_form = QtWidgets.QFormLayout() right_form = QtWidgets.QFormLayout() @@ -169,12 +168,24 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): from NanoVNASaver.NanoVNASaver import NanoVNASaver if self.location != -1: im50, re50, vswr = NanoVNASaver.vswr(s11data[self.location]) - rp = (re50 ** 2 + im50 ** 2) / re50 - xp = (re50 ** 2 + im50 ** 2) / im50 - re50 = round(re50, 4 - max(0, math.floor(math.log10(abs(re50))))) - rp = round(rp, 4 - max(0, math.floor(math.log10(abs(rp))))) - im50 = round(im50, 4 - max(0, math.floor(math.log10(abs(im50))))) - xp = round(xp, 4 - max(0, math.floor(math.log10(abs(xp))))) + if re50 > 0: + rp = (re50 ** 2 + im50 ** 2) / re50 + rp = round(rp, 4 - max(0, math.floor(math.log10(abs(rp))))) + + re50 = round(re50, 4 - max(0, math.floor(math.log10(abs(re50))))) + else: + rp = 0 + re50 = 0 + + if im50 > 0: + xp = (re50 ** 2 + im50 ** 2) / im50 + xp = round(xp, 4 - max(0, math.floor(math.log10(abs(xp))))) + else: + xp = 0 + + if im50 != 0: + im50 = round(im50, 4 - max(0, math.floor(math.log10(abs(im50))))) + if im50 < 0: im50str = " -j" + str(-1 * im50) else: @@ -190,7 +201,6 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): self.impedance_label.setText(str(re50) + im50str) self.parallel_r_label.setText(str(rp) + " \N{OHM SIGN}") self.parallel_x_label.setText(xpstr) - #self.returnloss_label.setText(str(round(20 * math.log10((vswr - 1) / (vswr + 1)), 3)) + " dB") self.returnloss_label.setText(str(round(NanoVNASaver.gain(s11data[self.location]), 3)) + " dB") capacitance = NanoVNASaver.capacitanceEquivalent(im50, s11data[self.location].freq) inductance = NanoVNASaver.inductanceEquivalent(im50, s11data[self.location].freq) @@ -200,7 +210,12 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): if vswr < 0: vswr = "-" self.vswr_label.setText(str(vswr)) - self.quality_factor_label.setText(str(round(NanoVNASaver.qualifyFactor(s11data[self.location]), 1))) + q = NanoVNASaver.qualifyFactor(s11data[self.location]) + if q > 10000 or q < 0: + q_str = "\N{INFINITY}" + else: + q_str = str(round(q, 1)) + self.quality_factor_label.setText(q_str) self.s11_phase_label.setText( str(round(PhaseChart.angle(s11data[self.location]), 2)) + "\N{DEGREE SIGN}") if len(s21data) == len(s11data): diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index e85d470d..f3a80f0b 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -795,10 +795,10 @@ def vswr(data: Datapoint): def qualifyFactor(data: Datapoint): im50, re50, _ = NanoVNASaver.vswr(data) if re50 != 0: - Q = im50 / re50 + Q = abs(im50 / re50) else: - Q = 0 - return abs(Q) + Q = -1 + return Q @staticmethod def capacitanceEquivalent(im50, freq) -> str: @@ -818,7 +818,7 @@ def capacitanceEquivalent(im50, freq) -> str: def inductanceEquivalent(im50, freq) -> str: if freq == 0: return "- nH" - inductance = im50 * 1000000000/ (freq * 2 * math.pi) + inductance = im50 * 1000000000 / (freq * 2 * math.pi) if abs(inductance) > 10000: return str(round(inductance / 1000, 2)) + " μH" elif abs(inductance) > 1000: From dab83fdedf38eaf5ee3cc6d6429866270965a557 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 14:36:46 +0200 Subject: [PATCH 09/17] - More handling of weird values - Draw lines heading out the bottom of charts in a reasonable manner --- NanoVNASaver/Chart.py | 3 +++ NanoVNASaver/Marker.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 34213bf1..83bcde8f 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -420,6 +420,9 @@ def getPlotable(self, x, y, distantx, distanty): if distanty < self.topMargin: p3 = np.array([self.leftMargin, self.topMargin]) p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin]) + elif distanty > self.topMargin + self.chartHeight: + p3 = np.array([self.leftMargin, self.topMargin + self.chartHeight]) + p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight]) else: return x, y da = p2 - p1 diff --git a/NanoVNASaver/Marker.py b/NanoVNASaver/Marker.py index 9697fe6a..624cd516 100644 --- a/NanoVNASaver/Marker.py +++ b/NanoVNASaver/Marker.py @@ -171,17 +171,22 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): if re50 > 0: rp = (re50 ** 2 + im50 ** 2) / re50 rp = round(rp, 4 - max(0, math.floor(math.log10(abs(rp))))) + rpstr = str(rp) + " \N{OHM SIGN}" re50 = round(re50, 4 - max(0, math.floor(math.log10(abs(re50))))) else: - rp = 0 + rpstr = "- \N{OHM SIGN}" re50 = 0 if im50 > 0: xp = (re50 ** 2 + im50 ** 2) / im50 xp = round(xp, 4 - max(0, math.floor(math.log10(abs(xp))))) + if xp < 0: + xpstr = NanoVNASaver.capacitanceEquivalent(xp, s11data[self.location].freq) + else: + xpstr = NanoVNASaver.inductanceEquivalent(xp, s11data[self.location].freq) else: - xp = 0 + xpstr = "- \N{OHM SIGN}" if im50 != 0: im50 = round(im50, 4 - max(0, math.floor(math.log10(abs(im50))))) @@ -192,14 +197,9 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): im50str = " +j" + str(im50) im50str += " \N{OHM SIGN}" - if xp < 0: - xpstr = NanoVNASaver.capacitanceEquivalent(xp, s11data[self.location].freq) - else: - xpstr = NanoVNASaver.inductanceEquivalent(xp, s11data[self.location].freq) - self.frequency_label.setText(NanoVNASaver.formatFrequency(s11data[self.location].freq)) self.impedance_label.setText(str(re50) + im50str) - self.parallel_r_label.setText(str(rp) + " \N{OHM SIGN}") + self.parallel_r_label.setText(rpstr) self.parallel_x_label.setText(xpstr) self.returnloss_label.setText(str(round(NanoVNASaver.gain(s11data[self.location]), 3)) + " dB") capacitance = NanoVNASaver.capacitanceEquivalent(im50, s11data[self.location].freq) From e771c33566f5518cfbee0d82643c3d4a7f1da3cd Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 14:53:01 +0200 Subject: [PATCH 10/17] - Debug is required for alpha versions --- NanoVNASaver/__main__.py | 4 ++++ NanoVNASaver/about.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/__main__.py b/NanoVNASaver/__main__.py index c2df07a3..63c554be 100644 --- a/NanoVNASaver/__main__.py +++ b/NanoVNASaver/__main__.py @@ -21,6 +21,7 @@ from PyQt5 import QtWidgets, QtCore from .NanoVNASaver import NanoVNASaver +from .about import debug def main(): @@ -44,6 +45,9 @@ def main(): print("You must enter a file name when using -D") return + if debug: + console_log_level = logging.DEBUG + logger = logging.getLogger("NanoVNASaver") logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index aed557b2..fd239649 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -15,4 +15,4 @@ # along with this program. If not, see . version = '0.1.0alpha' - +debug = True From ab55b7b7bb79aba25fcc3b489d5a52b06d249687 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 16:18:34 +0200 Subject: [PATCH 11/17] - LogMag charts were upside down! --- NanoVNASaver/Chart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 83bcde8f..fa022e54 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -1060,8 +1060,8 @@ def getYPosition(self, d: Datapoint) -> int: @staticmethod def logMag(p: Datapoint) -> float: - from NanoVNASaver.NanoVNASaver import NanoVNASaver - return NanoVNASaver.gain(p) + from NanoVNASaver.NanoVNASaver import NanoVNASaver + return -NanoVNASaver.gain(p) class QualityFactorChart(FrequencyChart): From 4cef7347bd80a4c8d0dbcfb6dfdda2c44af314b7 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 17:08:34 +0200 Subject: [PATCH 12/17] - Set default file extension for calibration files --- NanoVNASaver/Calibration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 2da9a979..eb956c74 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -413,7 +413,9 @@ def saveCalibration(self): logger.debug("Attempted to save an uncalculated calibration.") self.app.showError("Cannot save an unapplied calibration state.") return - filename, _ = QtWidgets.QFileDialog.getSaveFileName(filter="Calibration Files (*.cal);;All files (*.*)") + filedialog = QtWidgets.QFileDialog(self) + filedialog.setDefaultSuffix("cal") + filename, _ = filedialog.getSaveFileName(filter="Calibration Files (*.cal);;All files (*.*)") self.app.calibration.notes = self.notes_textedit.toPlainText().splitlines() if filename and self.app.calibration.saveCalibration(filename): self.app.settings.setValue("CalibrationFile", filename) From 3f2cdace803dc0c0ed877c0b4e320d3cd572a51c Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 1 Oct 2019 17:38:46 +0200 Subject: [PATCH 13/17] - Notify the user if all data is outside the fixed frequency span --- NanoVNASaver/Chart.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index fa022e54..66722716 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -350,6 +350,14 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) self.drawChart(qp) self.drawValues(qp) + if len(self.data) > 0 and (self.data[0].freq < self.fstart or self.data[len(self.data)-1].freq > self.fstop) \ + and (len(self.reference) == 0 or self.reference[0].freq < self.fstart or self.reference[len(self.reference)-1].freq > self.fstop): + # Data outside frequency range + qp.setBackgroundMode(QtCore.Qt.OpaqueMode) + qp.setBackground(self.backgroundColor) + qp.setPen(self.textColor) + qp.drawText(self.leftMargin + self.chartWidth/2 - 70, self.topMargin + self.chartHeight/2 - 20, + "Data outside frequency span") qp.end() def drawBands(self, qp, fstart, fstop): @@ -958,12 +966,6 @@ def __init__(self, name=""): self.setPalette(pal) self.setAutoFillBackground(True) - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: - qp = QtGui.QPainter(self) - self.drawChart(qp) - self.drawValues(qp) - qp.end() - def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(3, 15, self.name + " (dB)") From f5f67933b041ae7a7b3f5093dd8a5ee0d8343113 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Tue, 1 Oct 2019 20:42:56 +0200 Subject: [PATCH 14/17] - Prevent selecting higher min values than max, and vice versa --- NanoVNASaver/Chart.py | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 66722716..09afdefa 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -260,10 +260,18 @@ def contextMenuEvent(self, event): def setFixedSpan(self, fixed_span: bool): self.fixedSpan = fixed_span + if fixed_span and self.minFrequency >= self.maxFrequency: + self.fixedSpan = False + self.action_automatic.setChecked(True) + self.action_fixed_span.setChecked(False) self.update() def setFixedValues(self, fixed_values: bool): self.fixedValues = fixed_values + if fixed_values and self.minDisplayValue >= self.maxDisplayValue: + self.fixedValues = False + self.y_action_automatic.setChecked(True) + self.y_action_fixed_span.setChecked(False) self.update() def setMinimumFrequency(self): @@ -273,7 +281,7 @@ def setMinimumFrequency(self): if not selected: return min_freq = NanoVNASaver.parseFrequency(min_freq_str) - if min_freq > 0: + if min_freq > 0 and not (self.fixedSpan and min_freq >= self.maxFrequency): self.minFrequency = min_freq if self.fixedSpan: self.update() @@ -285,7 +293,7 @@ def setMaximumFrequency(self): if not selected: return max_freq = NanoVNASaver.parseFrequency(max_freq_str) - if max_freq > 0: + if max_freq > 0 and not (self.fixedSpan and max_freq <= self.minFrequency): self.maxFrequency = max_freq if self.fixedSpan: self.update() @@ -295,7 +303,8 @@ def setMinimumValue(self): "Set minimum value", value=self.minDisplayValue) if not selected: return - self.minDisplayValue = min_val + if not (self.fixedValues and min_val >= self.maxDisplayValue): + self.minDisplayValue = min_val if self.fixedValues: self.update() @@ -304,7 +313,8 @@ def setMaximumValue(self): "Set maximum value", value=self.maxDisplayValue) if not selected: return - self.maxDisplayValue = max_val + if not (self.fixedValues and max_val <= self.minDisplayValue): + self.maxDisplayValue = max_val if self.fixedValues: self.update() @@ -350,8 +360,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) self.drawChart(qp) self.drawValues(qp) - if len(self.data) > 0 and (self.data[0].freq < self.fstart or self.data[len(self.data)-1].freq > self.fstop) \ - and (len(self.reference) == 0 or self.reference[0].freq < self.fstart or self.reference[len(self.reference)-1].freq > self.fstop): + if len(self.data) > 0\ + and (self.data[0].freq > self.fstop or self.data[len(self.data)-1].freq < self.fstart) \ + and (len(self.reference) == 0 or self.reference[0].freq > self.fstop or self.reference[len(self.reference)-1].freq < self.fstart): # Data outside frequency range qp.setBackgroundMode(QtCore.Qt.OpaqueMode) qp.setBackground(self.backgroundColor) @@ -1598,7 +1609,8 @@ def setMinimumRealValue(self): "Set minimum real value", value=self.minDisplayReal) if not selected: return - self.minDisplayReal = min_val + if not (self.fixedValues and min_val >= self.maxDisplayReal): + self.minDisplayReal = min_val if self.fixedValues: self.update() @@ -1607,7 +1619,8 @@ def setMaximumRealValue(self): "Set maximum real value", value=self.maxDisplayReal) if not selected: return - self.maxDisplayReal = max_val + if not (self.fixedValues and max_val <= self.minDisplayReal): + self.maxDisplayReal = max_val if self.fixedValues: self.update() @@ -1616,7 +1629,8 @@ def setMinimumImagValue(self): "Set minimum imaginary value", value=self.minDisplayImag) if not selected: return - self.minDisplayImag = min_val + if not (self.fixedValues and min_val >= self.maxDisplayImag): + self.minDisplayImag = min_val if self.fixedValues: self.update() @@ -1625,10 +1639,19 @@ def setMaximumImagValue(self): "Set maximum imaginary value", value=self.maxDisplayImag) if not selected: return - self.maxDisplayImag = max_val + if not (self.fixedValues and max_val <= self.minDisplayImag): + self.maxDisplayImag = max_val if self.fixedValues: self.update() + def setFixedValues(self, fixed_values: bool): + self.fixedValues = fixed_values + if fixed_values and (self.minDisplayReal >= self.maxDisplayReal or self.minDisplayImag > self.maxDisplayImag): + self.fixedValues = False + self.y_action_automatic.setChecked(True) + self.y_action_fixed_span.setChecked(False) + self.update() + def contextMenuEvent(self, event): self.action_set_fixed_start.setText("Start (" + Chart.shortenFrequency(self.minFrequency) + ")") self.action_set_fixed_stop.setText("Stop (" + Chart.shortenFrequency(self.maxFrequency) + ")") From 2a98deb8b1d6524a5d8b32b30c84185023d8915a Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 2 Oct 2019 09:54:24 +0200 Subject: [PATCH 15/17] - Sweep time should be in local time, not UTC - Parallel reactance can be negative - Small values of Q should have more decimal places - New readme - 0.1.0 release candidate --- NanoVNASaver/Marker.py | 12 +++++-- NanoVNASaver/NanoVNASaver.py | 4 +-- NanoVNASaver/about.py | 4 +-- README.md | 69 +++++++++++++++++++++++++----------- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/NanoVNASaver/Marker.py b/NanoVNASaver/Marker.py index 624cd516..2211049d 100644 --- a/NanoVNASaver/Marker.py +++ b/NanoVNASaver/Marker.py @@ -178,7 +178,7 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): rpstr = "- \N{OHM SIGN}" re50 = 0 - if im50 > 0: + if im50 != 0: xp = (re50 ** 2 + im50 ** 2) / im50 xp = round(xp, 4 - max(0, math.floor(math.log10(abs(xp))))) if xp < 0: @@ -186,7 +186,7 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): else: xpstr = NanoVNASaver.inductanceEquivalent(xp, s11data[self.location].freq) else: - xpstr = "- \N{OHM SIGN}" + xpstr = "-" if im50 != 0: im50 = round(im50, 4 - max(0, math.floor(math.log10(abs(im50))))) @@ -213,8 +213,14 @@ def updateLabels(self, s11data: List[Datapoint], s21data: List[Datapoint]): q = NanoVNASaver.qualifyFactor(s11data[self.location]) if q > 10000 or q < 0: q_str = "\N{INFINITY}" - else: + elif q > 1000: + q_str = str(round(q, 0)) + elif q > 100: q_str = str(round(q, 1)) + elif q > 10: + q_str = str(round(q, 2)) + else: + q_str = str(round(q, 3)) self.quality_factor_label.setText(q_str) self.s11_phase_label.setText( str(round(PhaseChart.angle(s11data[self.location]), 2)) + "\N{DEGREE SIGN}") diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index f3a80f0b..97be0d2d 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -18,7 +18,7 @@ import math import sys import threading -from time import sleep, strftime, gmtime +from time import sleep, strftime, localtime from typing import List, Tuple import numpy as np @@ -719,7 +719,7 @@ def saveData(self, data, data12, source=None): if source is not None: self.sweepSource = source else: - self.sweepSource = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + self.sweepSource = strftime("%Y-%m-%d %H:%M:%S", localtime()) def dataUpdated(self): if self.dataLock.acquire(blocking=True): diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index fd239649..e89face3 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -14,5 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -version = '0.1.0alpha' -debug = True +version = '0.1.0' +debug = False diff --git a/README.md b/README.md index fa223b9a..d128c0d1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - - NanoVNASaver ============ A multiplatform tool to save Touchstone files from the NanoVNA, sweep frequency spans in segments to gain more than @@ -13,17 +11,20 @@ This software connects to a NanoVNA and extracts the data for display on a compu Current features: - Reading data from a NanoVNA - Splitting a frequency range into multiple segments to increase resolution (tried up to >10k points) +- Averaging data for better results particularly at higher frequencies - Displaying data on multiple chart types, such as Smith, LogMag, Phase and VSWR-charts, for both S11 and S21 -- Displaying markers, and the impedance, VSWR etc. at these locations +- Displaying markers, and the impedance, VSWR, Q, equivalent capacitance/inductance etc. at these locations +- Displaying customizable frequency bands as reference, for example amateur radio bands - Exporting and importing 1-port and 2-port Touchstone files - TDR function (measurement of cable length) - Display of both an active and a reference trace - Live updates of data from the NanoVNA, including for multi-segment sweeps - In-application calibration, including compensation for non-ideal calibration standards -- Customizable display options +- Customizable display options, including "dark mode" +- Exporting images of plotted values -0.0.10: -![Screenshot of version 0.0.10](https://i.imgur.com/0pzMk8O.png) +0.1.0: +![Screenshot of version 0.1.0](https://i.imgur.com/OHlq9oW.png) ## Running the application @@ -32,7 +33,11 @@ Current features: The software was written in Python on Windows, using Pycharm, and the modules PyQT5, numpy and pyserial. #### Binary releases -You can find binary (.exe) releases for Windows at https://github.com/mihtjel/nanovna-saver/releases +You can find the latest binary (.exe) release for Windows at https://github.com/mihtjel/nanovna-saver/releases/latest + +The downloadable executable runs directly, and requires no installation. For Windows 7, it does require Service Pack 1 +and [Microsoft VC++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads). +For most users, this is already installed. #### Installation and Use with pip @@ -57,7 +62,7 @@ In order to run this app in Linux environment, you'll need the following package * `python3-pyqt5` * `numpy` #### Ubuntu 18.04 & 19.04 -#### Installation and Use with pip +##### Installation and Use with pip 1. Install python3.7 and pip sudo apt install python3.7 python3-pip @@ -89,7 +94,8 @@ In order to run this app in Linux environment, you'll need the following package 3. NanoVNASaver Installation - git clone https://github.com/mihtjel/nanovna-saver && cd nanovna-saver + git clone https://github.com/mihtjel/nanovna-saver + cd nanovna-saver 4. Change PyQt restriction in setup.py `PyQt5==5.11.2` to `PyQt5` @@ -102,28 +108,48 @@ In order to run this app in Linux environment, you'll need the following package ## Using the software Connect your NanoVNA to a serial port, and enter this serial port in the serial port box. If the NanoVNA is -connected before the application starts, it should be automatically detected. Click "Connect to NanoVNA" to connect. +connected before the application starts, it should be automatically detected. Otherwise, click "Rescan". Click +"Connect to NanoVNA" to connect. The app can collect multiple segments to get more accurate measurements. Enter the number of segments to be done in the "Segments" box. Each segment is 101 data points, and takes about 1.5 seconds to complete. -Marker frequencies are entered in Hz, or suffixed with k or M. Scientific notation (6.5e6 for 6.5MHz) also works. -The marker readout boxes show the actual frequency where the values are taken. Marker readouts can be hidden using the -"hide data" button when not needed. +Frequencies are entered in Hz, or suffixed with k or M. Scientific notation (6.5e6 for 6.5MHz) also works. + +Markers can be manually entered, or controlled using the mouse. For mouse control, select the active marker using the +radio buttons, or hold "shift" while clicking to drag the nearest marker. The marker readout boxes show the actual +frequency where values are measured. Marker readouts can be hidden using the "hide data" button when not needed. Display settings are available under "Display setup". These allow changing the chart colours, the application font size and which graphs are displayed. The settings are saved between program starts. -#### Calibration +### Calibration +_Before using NanoVNA-Saver, please ensure that the device itself is in a reasonable calibration state._ A calibration +of both ports across the entire frequency span, saved to save slot 0, is sufficient. If the NanoVNA is completely +uncalibrated, its readings may be outside the range accepted by the application. + In-application calibration is available, either assuming ideal standards, or with relevant standard correction. To -calibrate, sweep each standard in turn, and press the relevant button in the calibration window. After applying the -calibration, you may save it by entering a file location and name, and pressing "Save calibration". Conversely, a -saved calibration can be loaded. The file location and name is saved between program starts. -![Screenshot of Calibration Window](https://i.imgur.com/F5X2ECZ.png) +manually calibrate, sweep each standard in turn, and press the relevant button in the calibration window. For assisted +calibration, press the "Calibration assistant" button. If desired, enter a note in the provided field describing the +conditions under which the calibration was performed. + +Calibration results may be saved and loaded using the provided buttons at the bottom of the window. Notes are saved +and loaded along with the calibration data. +![Screenshot of Calibration Window](https://i.imgur.com/k6sqAVd.png) -#### TDR +Users of known characterized calibration standard sets can enter the data for these, and save the sets.and + +_Currently, load capacitance and deleting calibration sets is unsupported_ + +### TDR To get accurate TDR measurements, calibrate the device, and attach the cable to be measured at the calibration plane - -ie. at the same position where the calibration load would be attached. +ie. at the same position where the calibration load would be attached. Open the "Time Domain Reflectometry" window, and +select the correct cable type, or manually enter a propagation factor. + +### Frequency bands +Open the "Display setup" window to configure the display of frequency bands. By clicking "show bands", predefined +frequency bands will be shown on the frequency-based charts. Click manage bands to change which bands are shown, and +the frequency limits of each. Bands default and reset to European amateur radio band frequencies. ## License This software is licensed under version 3 of the GNU General Public License. It comes with NO WARRANTY. @@ -144,7 +170,8 @@ TDR inspiration shamelessly stolen from the work of Salil (VU2CWA) at https://nu TDR cable types by Larry Goga. Bugfixes and Python installation work by Ohan Smit -Thanks to everyone who have tested, commented and inspired. +Thanks to everyone who have tested, commented and inspired. Particular thanks go to the alpha testing crew who suffer +the early instability of new versions. This software is available free of charge. If you read all this way, and you *still* want to support it, you may donate to the developer using the button below: From 669d111c2f23c0135a588474599e48e3049045ad Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 2 Oct 2019 09:57:25 +0200 Subject: [PATCH 16/17] - Layout fixes --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d128c0d1..60673ea6 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ conditions under which the calibration was performed. Calibration results may be saved and loaded using the provided buttons at the bottom of the window. Notes are saved and loaded along with the calibration data. + ![Screenshot of Calibration Window](https://i.imgur.com/k6sqAVd.png) Users of known characterized calibration standard sets can enter the data for these, and save the sets.and @@ -167,8 +168,10 @@ changes back to the community. Original application by Rune B. Broberg (5Q5R) TDR inspiration shamelessly stolen from the work of Salil (VU2CWA) at https://nuclearrambo.com/wordpress/accurately-measuring-cable-length-with-nanovna/ + TDR cable types by Larry Goga. -Bugfixes and Python installation work by Ohan Smit + +Bugfixes and Python installation work by Ohan Smit. Thanks to everyone who have tested, commented and inspired. Particular thanks go to the alpha testing crew who suffer the early instability of new versions. From 5f4dd68ab7ace633a60752c8c8eef3ec0c9a03ef Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 2 Oct 2019 10:07:38 +0200 Subject: [PATCH 17/17] - Icons --- NanoVNASaver/Calibration.py | 1 + NanoVNASaver/NanoVNASaver.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index eb956c74..29e0db43 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -40,6 +40,7 @@ def __init__(self, app): self.setMinimumSize(450, 600) self.setWindowTitle("Calibration") + self.setWindowIcon(self.app.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 97be0d2d..b60d42cb 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -49,7 +49,12 @@ class NanoVNASaver(QtWidgets.QWidget): def __init__(self): super().__init__() - self.setWindowIcon(QtGui.QIcon("icon_48x48.png")) + if getattr(sys, 'frozen', False): + logger.debug("Running from pyinstaller bundle") + self.icon = QtGui.QIcon(sys._MEIPASS + "/icon_48x48.png") + else: + self.icon = QtGui.QIcon("icon_48x48.png") + self.setWindowIcon(self.icon) self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, @@ -386,6 +391,7 @@ def __init__(self): self.fileWindow = QtWidgets.QWidget() self.fileWindow.setWindowTitle("Files") + self.fileWindow.setWindowIcon(self.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self.fileWindow, self.fileWindow.hide) file_window_layout = QtWidgets.QVBoxLayout() self.fileWindow.setLayout(file_window_layout) @@ -1072,6 +1078,7 @@ def __init__(self, app: NanoVNASaver): self.app = app self.setWindowTitle("Display settings") + self.setWindowIcon(self.app.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -1439,6 +1446,7 @@ def __init__(self, app: NanoVNASaver): self.distance_axis = [] self.setWindowTitle("TDR") + self.setWindowIcon(self.app.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -1550,6 +1558,7 @@ def __init__(self, app: NanoVNASaver): self.app = app self.setWindowTitle("Sweep settings") + self.setWindowIcon(self.app.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -1590,6 +1599,7 @@ def __init__(self, app): self.app: NanoVNASaver = app self.setWindowTitle("Manage bands") + self.setWindowIcon(self.app.icon) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)