mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-11-05 01:40:01 +00:00
Basic implementation of calibration.
This commit is contained in:
parent
acb328eea2
commit
e2eafb4e6d
6 changed files with 394 additions and 26 deletions
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import numpy as np
|
||||||
from serial.tools.list_ports import comports
|
from serial.tools.list_ports import comports
|
||||||
from PyQt6 import QtSerialPort
|
from PyQt6 import QtSerialPort
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
|
||||||
|
@ -34,9 +35,16 @@ class AutoTMController(ModuleController):
|
||||||
def start_frequency_sweep(self, start_frequency : float, stop_frequency : float) -> None:
|
def start_frequency_sweep(self, start_frequency : float, stop_frequency : float) -> None:
|
||||||
""" This starts a frequency sweep on the device in the specified range."""
|
""" This starts a frequency sweep on the device in the specified range."""
|
||||||
logger.debug("Starting frequency sweep from %s to %s", start_frequency, stop_frequency)
|
logger.debug("Starting frequency sweep from %s to %s", start_frequency, stop_frequency)
|
||||||
|
# We create the frequency sweep spinner dialog
|
||||||
|
self.module.model.clear_data_points()
|
||||||
|
self.module.view.create_frequency_sweep_spinner_dialog()
|
||||||
# Print the command 'f <start> <stop>' to the serial connection
|
# Print the command 'f <start> <stop>' to the serial connection
|
||||||
|
try:
|
||||||
command = "f %s %s" % (start_frequency, stop_frequency)
|
command = "f %s %s" % (start_frequency, stop_frequency)
|
||||||
self.module.model.serial.write(command.encode('utf-8'))
|
self.module.model.serial.write(command.encode('utf-8'))
|
||||||
|
except AttributeError:
|
||||||
|
logger.error("Could not start frequency sweep. No device connected.")
|
||||||
|
|
||||||
|
|
||||||
def on_ready_read(self) -> None:
|
def on_ready_read(self) -> None:
|
||||||
"""This method is called when data is received from the serial connection. """
|
"""This method is called when data is received from the serial connection. """
|
||||||
|
@ -44,11 +52,108 @@ class AutoTMController(ModuleController):
|
||||||
while serial.canReadLine():
|
while serial.canReadLine():
|
||||||
text = serial.readLine().data().decode()
|
text = serial.readLine().data().decode()
|
||||||
text = text.rstrip('\r\n')
|
text = text.rstrip('\r\n')
|
||||||
logger.debug("Received data: %s", text)
|
# logger.debug("Received data: %s", text)
|
||||||
if text.startswith("f"):
|
# If the text starts with 'f' and the frequency sweep spinner is visible we know that the data is a data point
|
||||||
|
if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible():
|
||||||
text = text[1:].split("r")
|
text = text[1:].split("r")
|
||||||
frequency = float(text[0])
|
frequency = float(text[0])
|
||||||
return_loss = float(text[1])
|
return_loss, phase = map(float, text[1].split("p"))
|
||||||
self.module.model.add_data_point(frequency, return_loss)
|
self.module.model.add_data_point(frequency, return_loss, phase)
|
||||||
|
elif text.startswith("r") and self.module.model.active_calibration == None:
|
||||||
|
logger.debug("Measurement finished")
|
||||||
|
self.module.view.plot_data()
|
||||||
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
|
elif text.startswith("r") and self.module.model.active_calibration == "short":
|
||||||
|
logger.debug("Short calibration finished")
|
||||||
|
self.module.model.short_calibration = self.module.model.data_points.copy()
|
||||||
|
self.module.model.active_calibration = None
|
||||||
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
|
elif text.startswith("r") and self.module.model.active_calibration == "open":
|
||||||
|
logger.debug("Open calibration finished")
|
||||||
|
self.module.model.open_calibration = self.module.model.data_points.copy()
|
||||||
|
self.module.model.active_calibration = None
|
||||||
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
|
elif text.startswith("r") and self.module.model.active_calibration == "load":
|
||||||
|
logger.debug("Load calibration finished")
|
||||||
|
self.module.model.load_calibration = self.module.model.data_points.copy()
|
||||||
|
self.module.model.active_calibration = None
|
||||||
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
else:
|
else:
|
||||||
self.module.view.add_info_text(text)
|
self.module.view.add_info_text(text)
|
||||||
|
|
||||||
|
def on_short_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||||
|
"""This method is called when the short calibration button is pressed.
|
||||||
|
It starts a frequency sweep in the specified range and then starts a short calibration.
|
||||||
|
"""
|
||||||
|
logger.debug("Starting short calibration")
|
||||||
|
self.module.model.init_short_calibration()
|
||||||
|
self.start_frequency_sweep(start_frequency, stop_frequency)
|
||||||
|
|
||||||
|
def on_open_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||||
|
"""This method is called when the open calibration button is pressed.
|
||||||
|
It starts a frequency sweep in the specified range and then starts an open calibration.
|
||||||
|
"""
|
||||||
|
logger.debug("Starting open calibration")
|
||||||
|
self.module.model.init_open_calibration()
|
||||||
|
self.start_frequency_sweep(start_frequency, stop_frequency)
|
||||||
|
|
||||||
|
def on_load_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||||
|
"""This method is called when the load calibration button is pressed.
|
||||||
|
It starts a frequency sweep in the specified range and then loads a calibration.
|
||||||
|
"""
|
||||||
|
logger.debug("Starting load calibration")
|
||||||
|
self.module.model.init_load_calibration()
|
||||||
|
self.start_frequency_sweep(start_frequency, stop_frequency)
|
||||||
|
|
||||||
|
def calculate_calibration(self) -> None:
|
||||||
|
"""This method is called when the calculate calibration button is pressed.
|
||||||
|
It calculates the calibration from the short, open and calibration data points.
|
||||||
|
"""
|
||||||
|
logger.debug("Calculating calibration")
|
||||||
|
# First we check if the short and open calibration data points are available
|
||||||
|
if self.module.model.short_calibration == None:
|
||||||
|
logger.error("No short calibration data points available")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.module.model.open_calibration == None:
|
||||||
|
logger.error("No open calibration data points available")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.module.model.load_calibration == None:
|
||||||
|
logger.error("No load calibration data points available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Then we check if the short, open and load calibration data points have the same length
|
||||||
|
if len(self.module.model.short_calibration) != len(self.module.model.open_calibration) or len(self.module.model.short_calibration) != len(self.module.model.load_calibration):
|
||||||
|
logger.error("The short, open and load calibration data points do not have the same length")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Then we calculate the calibration
|
||||||
|
ideal_gamma_short = -1
|
||||||
|
ideal_gamma_open = 1
|
||||||
|
ideal_gamma_load = 0
|
||||||
|
|
||||||
|
short_calibration = [10 **(-returnloss_s[1] /6 / 24 / 20) for returnloss_s in self.module.model.short_calibration]
|
||||||
|
open_calibration = [10 **(-returnloss_o[1] / 6 / 24 / 20) for returnloss_o in self.module.model.open_calibration]
|
||||||
|
load_calibration = [10 **(-returnloss_l[1] / 6 / 24 / 20) for returnloss_l in self.module.model.load_calibration]
|
||||||
|
|
||||||
|
e_00s = []
|
||||||
|
e11s = []
|
||||||
|
delta_es = []
|
||||||
|
for gamma_s, gamma_o, gamma_l in zip(short_calibration, open_calibration, load_calibration):
|
||||||
|
A = np.array([
|
||||||
|
[1, ideal_gamma_short * gamma_s, -ideal_gamma_short],
|
||||||
|
[1, ideal_gamma_open * gamma_o, -ideal_gamma_open],
|
||||||
|
[1, ideal_gamma_load * gamma_l, -ideal_gamma_load]
|
||||||
|
])
|
||||||
|
|
||||||
|
B = np.array([gamma_s, gamma_o, gamma_l])
|
||||||
|
|
||||||
|
# Solve the system
|
||||||
|
e_00, e11, delta_e = np.linalg.lstsq(A, B, rcond=None)[0]
|
||||||
|
|
||||||
|
e_00s.append(e_00)
|
||||||
|
e11s.append(e11)
|
||||||
|
delta_es.append(delta_e)
|
||||||
|
|
||||||
|
self.module.model.calibration = (e_00s, e11s, delta_es)
|
|
@ -1,17 +1,24 @@
|
||||||
import serial
|
import serial
|
||||||
|
import logging
|
||||||
from PyQt6.QtCore import pyqtSignal
|
from PyQt6.QtCore import pyqtSignal
|
||||||
from PyQt6.QtSerialPort import QSerialPort
|
from PyQt6.QtSerialPort import QSerialPort
|
||||||
from nqrduck.module.module_model import ModuleModel
|
from nqrduck.module.module_model import ModuleModel
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class AutoTMModel(ModuleModel):
|
class AutoTMModel(ModuleModel):
|
||||||
available_devices_changed = pyqtSignal(list)
|
available_devices_changed = pyqtSignal(list)
|
||||||
serial_changed = pyqtSignal(QSerialPort)
|
serial_changed = pyqtSignal(QSerialPort)
|
||||||
data_points_changed = pyqtSignal(list)
|
data_points_changed = pyqtSignal(list)
|
||||||
|
|
||||||
|
short_calibration_finished = pyqtSignal(list)
|
||||||
|
open_calibration_finished = pyqtSignal(list)
|
||||||
|
load_calibration_finished = pyqtSignal(list)
|
||||||
|
|
||||||
def __init__(self, module) -> None:
|
def __init__(self, module) -> None:
|
||||||
super().__init__(module)
|
super().__init__(module)
|
||||||
self.data_points = []
|
self.data_points = []
|
||||||
|
self.active_calibration = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_devices(self):
|
def available_devices(self):
|
||||||
|
@ -31,12 +38,74 @@ class AutoTMModel(ModuleModel):
|
||||||
self._serial = value
|
self._serial = value
|
||||||
self.serial_changed.emit(value)
|
self.serial_changed.emit(value)
|
||||||
|
|
||||||
def add_data_point(self, frequency : float, return_loss : float) -> None:
|
def add_data_point(self, frequency: float, return_loss: float, phase : float) -> None:
|
||||||
"""Add a data point to the model."""
|
"""Add a data point to the model."""
|
||||||
self.data_points.append((frequency, return_loss))
|
self.data_points.append((frequency, return_loss, phase))
|
||||||
self.data_points_changed.emit(self.data_points)
|
self.data_points_changed.emit(self.data_points)
|
||||||
|
|
||||||
def clear_data_points(self) -> None:
|
def clear_data_points(self) -> None:
|
||||||
"""Clear all data points from the model."""
|
"""Clear all data points from the model."""
|
||||||
self.data_points.clear()
|
self.data_points.clear()
|
||||||
self.data_points_changed.emit(self.data_points)
|
self.data_points_changed.emit(self.data_points)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_calibration(self):
|
||||||
|
return self._active_calibration
|
||||||
|
|
||||||
|
@active_calibration.setter
|
||||||
|
def active_calibration(self, value):
|
||||||
|
self._active_calibration = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_calibration(self):
|
||||||
|
return self._short_calibration
|
||||||
|
|
||||||
|
@short_calibration.setter
|
||||||
|
def short_calibration(self, value):
|
||||||
|
logger.debug("Setting short calibration")
|
||||||
|
self._short_calibration = value
|
||||||
|
self.short_calibration_finished.emit(value)
|
||||||
|
|
||||||
|
def init_short_calibration(self):
|
||||||
|
"""This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration."""
|
||||||
|
self.active_calibration = "short"
|
||||||
|
self.clear_data_points()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_calibration(self):
|
||||||
|
return self._open_calibration
|
||||||
|
|
||||||
|
@open_calibration.setter
|
||||||
|
def open_calibration(self, value):
|
||||||
|
logger.debug("Setting open calibration")
|
||||||
|
self._open_calibration = value
|
||||||
|
self.open_calibration_finished.emit(value)
|
||||||
|
|
||||||
|
def init_open_calibration(self):
|
||||||
|
"""This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration."""
|
||||||
|
self.active_calibration = "open"
|
||||||
|
self.clear_data_points()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def load_calibration(self):
|
||||||
|
return self._load_calibration
|
||||||
|
|
||||||
|
@load_calibration.setter
|
||||||
|
def load_calibration(self, value):
|
||||||
|
logger.debug("Setting load calibration")
|
||||||
|
self._load_calibration = value
|
||||||
|
self.load_calibration_finished.emit(value)
|
||||||
|
|
||||||
|
def init_load_calibration(self):
|
||||||
|
"""This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration."""
|
||||||
|
self.active_calibration = "load"
|
||||||
|
self.clear_data_points()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def calibration(self):
|
||||||
|
return self._calibration
|
||||||
|
|
||||||
|
@calibration.setter
|
||||||
|
def calibration(self, value):
|
||||||
|
logger.debug("Setting calibration")
|
||||||
|
self._calibration = value
|
||||||
|
|
|
@ -163,7 +163,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="pushButton">
|
<widget class="QPushButton" name="calibrationButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Calibrate</string>
|
<string>Calibrate</string>
|
||||||
</property>
|
</property>
|
||||||
|
|
BIN
src/nqrduck_autotm/resources/duck_kick.gif
Normal file
BIN
src/nqrduck_autotm/resources/duck_kick.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -1,9 +1,12 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from PyQt6.QtGui import QMovie
|
||||||
from PyQt6.QtSerialPort import QSerialPort
|
from PyQt6.QtSerialPort import QSerialPort
|
||||||
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication
|
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication, QHBoxLayout, QLineEdit, QPushButton, QDialog
|
||||||
from PyQt6.QtCore import pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSlot, Qt
|
||||||
from nqrduck.module.module_view import ModuleView
|
from nqrduck.module.module_view import ModuleView
|
||||||
|
from nqrduck.contrib.mplwidget import MplWidget
|
||||||
from .widget import Ui_Form
|
from .widget import Ui_Form
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -39,8 +42,8 @@ class AutoTMView(ModuleView):
|
||||||
float(self._ui_form.stopEdit.text())
|
float(self._ui_form.stopEdit.text())
|
||||||
))
|
))
|
||||||
|
|
||||||
# Connect the data points changed signal to the on_data_points_changed slot
|
# On clicking of the calibration button call the on_calibration_button_clicked method
|
||||||
self.module.model.data_points_changed.connect(self.on_data_points_changed)
|
self._ui_form.calibrationButton.clicked.connect(self.on_calibration_button_clicked)
|
||||||
|
|
||||||
# Add a vertical layout to the info box
|
# Add a vertical layout to the info box
|
||||||
self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout())
|
self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout())
|
||||||
|
@ -68,6 +71,14 @@ class AutoTMView(ModuleView):
|
||||||
ax.set_ylim(-100, 0)
|
ax.set_ylim(-100, 0)
|
||||||
self._ui_form.S11Plot.canvas.draw()
|
self._ui_form.S11Plot.canvas.draw()
|
||||||
|
|
||||||
|
def on_calibration_button_clicked(self) -> None:
|
||||||
|
"""This method is called when the calibration button is clicked.
|
||||||
|
It opens the calibration window.
|
||||||
|
"""
|
||||||
|
logger.debug("Calibration button clicked")
|
||||||
|
self.calibration_window = self.CalibrationWindow(self.module)
|
||||||
|
self.calibration_window.show()
|
||||||
|
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
def on_available_devices_changed(self, available_devices : list) -> None:
|
def on_available_devices_changed(self, available_devices : list) -> None:
|
||||||
"""Update the available devices list in the view. """
|
"""Update the available devices list in the view. """
|
||||||
|
@ -104,15 +115,32 @@ class AutoTMView(ModuleView):
|
||||||
self._ui_form.connectionLabel.setText("Disconnected")
|
self._ui_form.connectionLabel.setText("Disconnected")
|
||||||
logger.debug("Updated serial connection label")
|
logger.debug("Updated serial connection label")
|
||||||
|
|
||||||
@pyqtSlot(list)
|
def plot_data(self) -> None:
|
||||||
def on_data_points_changed(self, data_points : list) -> None:
|
|
||||||
"""Update the S11 plot with the current data points.
|
"""Update the S11 plot with the current data points.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data_points (list): List of data points to plot.
|
data_points (list): List of data points to plot.
|
||||||
"""
|
"""
|
||||||
x = [data_point[0] for data_point in data_points]
|
x = [data_point[0] for data_point in self.module.model.data_points]
|
||||||
y = [data_point[1] for data_point in data_points]
|
y = [(data_point[1] - 900) / 30 for data_point in self.module.model.data_points]
|
||||||
|
phase = [(data_point[2] - 900) / 10 for data_point in self.module.model.data_points]
|
||||||
|
|
||||||
|
# Calibration test:
|
||||||
|
#calibration = self.module.model.calibration
|
||||||
|
#e_00 = calibration[0]
|
||||||
|
#e11 = calibration[1]
|
||||||
|
#delta_e = calibration[2]
|
||||||
|
|
||||||
|
#y_corr = [(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(y)]
|
||||||
|
#import numpy as np
|
||||||
|
#y = [data_point[1] for data_point in self.module.model.data_points]
|
||||||
|
#open_calibration = [data_point[1] for data_point in self.module.model.open_calibration]
|
||||||
|
#load_calibration = [data_point[1] for data_point in self.module.model.load_calibration]
|
||||||
|
#short_calibration = [data_point[1] for data_point in self.module.model.short_calibration]
|
||||||
|
|
||||||
|
#y_corr = np.array(y) - np.array(load_calibration)
|
||||||
|
#y_corr = y_corr - np.mean(y_corr)
|
||||||
|
|
||||||
ax = self._ui_form.S11Plot.canvas.ax
|
ax = self._ui_form.S11Plot.canvas.ax
|
||||||
ax.clear()
|
ax.clear()
|
||||||
ax.set_xlabel("Frequency (MHz)")
|
ax.set_xlabel("Frequency (MHz)")
|
||||||
|
@ -120,6 +148,7 @@ class AutoTMView(ModuleView):
|
||||||
ax.set_title("S11")
|
ax.set_title("S11")
|
||||||
ax.grid(True)
|
ax.grid(True)
|
||||||
ax.plot(x, y)
|
ax.plot(x, y)
|
||||||
|
ax.plot(x, phase)
|
||||||
# make the y axis go down instead of up
|
# make the y axis go down instead of up
|
||||||
ax.invert_yaxis()
|
ax.invert_yaxis()
|
||||||
self._ui_form.S11Plot.canvas.draw()
|
self._ui_form.S11Plot.canvas.draw()
|
||||||
|
@ -140,3 +169,168 @@ class AutoTMView(ModuleView):
|
||||||
text_label.setStyleSheet("font-size: 25px;")
|
text_label.setStyleSheet("font-size: 25px;")
|
||||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
||||||
self._ui_form.scrollArea.verticalScrollBar().setValue(self._ui_form.scrollArea.verticalScrollBar().maximum())
|
self._ui_form.scrollArea.verticalScrollBar().setValue(self._ui_form.scrollArea.verticalScrollBar().maximum())
|
||||||
|
|
||||||
|
def create_frequency_sweep_spinner_dialog(self) -> None:
|
||||||
|
"""Creates a frequency sweep spinner dialog. """
|
||||||
|
self.frequency_sweep_spinner = self.FrequencySweepSpinner()
|
||||||
|
self.frequency_sweep_spinner.show()
|
||||||
|
|
||||||
|
class FrequencySweepSpinner(QDialog):
|
||||||
|
"""This class implements a spinner dialog that is shown during a frequency sweep."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Frequency sweep")
|
||||||
|
self.setModal(True)
|
||||||
|
self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
|
||||||
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
path = Path(__file__).parent
|
||||||
|
self.spinner_movie = QMovie(str(path / "resources/duck_kick.gif"))
|
||||||
|
self.spinner_label = QLabel(self)
|
||||||
|
self.spinner_label.setMovie(self.spinner_movie)
|
||||||
|
|
||||||
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.layout.addWidget(self.spinner_label)
|
||||||
|
|
||||||
|
self.spinner_movie.start()
|
||||||
|
|
||||||
|
|
||||||
|
class CalibrationWindow(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, module, parent=None):
|
||||||
|
super().__init__()
|
||||||
|
self.module = module
|
||||||
|
self.setParent(parent)
|
||||||
|
self.setWindowTitle("Calibration")
|
||||||
|
|
||||||
|
# Add vertical main layout
|
||||||
|
main_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# Add horizontal layout for the frequency range
|
||||||
|
frequency_layout = QHBoxLayout()
|
||||||
|
main_layout.addLayout(frequency_layout)
|
||||||
|
frequency_label = QLabel("Frequency range")
|
||||||
|
frequency_layout.addWidget(frequency_label)
|
||||||
|
start_edit = QLineEdit()
|
||||||
|
start_edit.setPlaceholderText("Start")
|
||||||
|
frequency_layout.addWidget(start_edit)
|
||||||
|
stop_edit = QLineEdit()
|
||||||
|
stop_edit.setPlaceholderText("Stop")
|
||||||
|
frequency_layout.addWidget(stop_edit)
|
||||||
|
unit_label = QLabel("MHz")
|
||||||
|
frequency_layout.addWidget(unit_label)
|
||||||
|
|
||||||
|
# Add horizontal layout for the calibration type
|
||||||
|
type_layout = QHBoxLayout()
|
||||||
|
main_layout.addLayout(type_layout)
|
||||||
|
|
||||||
|
# Add vertical layout for short calibration
|
||||||
|
short_layout = QVBoxLayout()
|
||||||
|
short_button = QPushButton("Short")
|
||||||
|
short_button.clicked.connect(lambda: self.module.controller.on_short_calibration(
|
||||||
|
float(start_edit.text()),
|
||||||
|
float(stop_edit.text())
|
||||||
|
))
|
||||||
|
# Short plot widget
|
||||||
|
self.short_plot = MplWidget()
|
||||||
|
short_layout.addWidget(self.short_plot)
|
||||||
|
short_layout.addWidget(short_button)
|
||||||
|
type_layout.addLayout(short_layout)
|
||||||
|
|
||||||
|
# Add vertical layout for open calibration
|
||||||
|
open_layout = QVBoxLayout()
|
||||||
|
open_button = QPushButton("Open")
|
||||||
|
open_button.clicked.connect(lambda: self.module.controller.on_open_calibration(
|
||||||
|
float(start_edit.text()),
|
||||||
|
float(stop_edit.text())
|
||||||
|
))
|
||||||
|
# Open plot widget
|
||||||
|
self.open_plot = MplWidget()
|
||||||
|
open_layout.addWidget(self.open_plot)
|
||||||
|
open_layout.addWidget(open_button)
|
||||||
|
type_layout.addLayout(open_layout)
|
||||||
|
|
||||||
|
# Add vertical layout for load calibration
|
||||||
|
load_layout = QVBoxLayout()
|
||||||
|
load_button = QPushButton("Load")
|
||||||
|
load_button.clicked.connect(lambda: self.module.controller.on_load_calibration(
|
||||||
|
float(start_edit.text()),
|
||||||
|
float(stop_edit.text())
|
||||||
|
))
|
||||||
|
# Load plot widget
|
||||||
|
self.load_plot = MplWidget()
|
||||||
|
load_layout.addWidget(self.load_plot)
|
||||||
|
load_layout.addWidget(load_button)
|
||||||
|
type_layout.addLayout(load_layout)
|
||||||
|
|
||||||
|
# Add vertical layout for save calibration
|
||||||
|
data_layout = QVBoxLayout()
|
||||||
|
# Export button
|
||||||
|
export_button = QPushButton("Export")
|
||||||
|
export_button.clicked.connect(self.on_export_button_clicked)
|
||||||
|
data_layout.addWidget(export_button)
|
||||||
|
# Import button
|
||||||
|
import_button = QPushButton("Import")
|
||||||
|
import_button.clicked.connect(self.on_import_button_clicked)
|
||||||
|
data_layout.addWidget(import_button)
|
||||||
|
# Apply button
|
||||||
|
apply_button = QPushButton("Apply calibration")
|
||||||
|
apply_button.clicked.connect(self.on_apply_button_clicked)
|
||||||
|
data_layout.addWidget(apply_button)
|
||||||
|
|
||||||
|
main_layout.addLayout(data_layout)
|
||||||
|
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
# Connect the calibration finished signals to the on_calibration_finished slot
|
||||||
|
self.module.model.short_calibration_finished.connect(self.on_short_calibration_finished)
|
||||||
|
self.module.model.open_calibration_finished.connect(self.on_open_calibration_finished)
|
||||||
|
self.module.model.load_calibration_finished.connect(self.on_load_calibration_finished)
|
||||||
|
|
||||||
|
def on_short_calibration_finished(self, short_calibration : list) -> None:
|
||||||
|
self.on_calibration_finished("short", self.short_plot, short_calibration)
|
||||||
|
|
||||||
|
def on_open_calibration_finished(self, open_calibration : list) -> None:
|
||||||
|
self.on_calibration_finished("open", self.open_plot, open_calibration)
|
||||||
|
|
||||||
|
def on_load_calibration_finished(self, load_calibration : list) -> None:
|
||||||
|
self.on_calibration_finished("load", self.load_plot, load_calibration)
|
||||||
|
|
||||||
|
def on_calibration_finished(self, type : str, widget: MplWidget, data :list) -> None:
|
||||||
|
"""This method is called when a calibration has finished.
|
||||||
|
It plots the calibration data on the given widget.
|
||||||
|
"""
|
||||||
|
x = [data_point[0] for data_point in data]
|
||||||
|
magnitude = [data_point[1] for data_point in data]
|
||||||
|
phase = [data_point[2] for data_point in data]
|
||||||
|
ax = widget.canvas.ax
|
||||||
|
ax.clear()
|
||||||
|
ax.set_xlabel("Frequency (MHz)")
|
||||||
|
ax.set_ylabel("S11 (dB)")
|
||||||
|
ax.set_title("S11")
|
||||||
|
ax.grid(True)
|
||||||
|
ax.plot(x, magnitude, label="Magnitude")
|
||||||
|
ax.plot(x, phase, label="Phase")
|
||||||
|
ax.legend()
|
||||||
|
# make the y axis go down instead of up
|
||||||
|
ax.invert_yaxis()
|
||||||
|
widget.canvas.draw()
|
||||||
|
widget.canvas.flush_events()
|
||||||
|
|
||||||
|
def on_export_button_clicked(self) -> None:
|
||||||
|
"""This method is called when the export button is clicked. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_import_button_clicked(self) -> None:
|
||||||
|
"""This method is called when the import button is clicked. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_apply_button_clicked(self) -> None:
|
||||||
|
"""This method is called when the apply button is clicked. """
|
||||||
|
self.module.controller.calculate_calibration()
|
||||||
|
# Close the calibration window
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -94,9 +94,9 @@ class Ui_Form(object):
|
||||||
self.startButton = QtWidgets.QPushButton(parent=Form)
|
self.startButton = QtWidgets.QPushButton(parent=Form)
|
||||||
self.startButton.setObjectName("startButton")
|
self.startButton.setObjectName("startButton")
|
||||||
self.verticalLayout_2.addWidget(self.startButton)
|
self.verticalLayout_2.addWidget(self.startButton)
|
||||||
self.pushButton = QtWidgets.QPushButton(parent=Form)
|
self.calibrationButton = QtWidgets.QPushButton(parent=Form)
|
||||||
self.pushButton.setObjectName("pushButton")
|
self.calibrationButton.setObjectName("calibrationButton")
|
||||||
self.verticalLayout_2.addWidget(self.pushButton)
|
self.verticalLayout_2.addWidget(self.calibrationButton)
|
||||||
self.pushButton_3 = QtWidgets.QPushButton(parent=Form)
|
self.pushButton_3 = QtWidgets.QPushButton(parent=Form)
|
||||||
self.pushButton_3.setObjectName("pushButton_3")
|
self.pushButton_3.setObjectName("pushButton_3")
|
||||||
self.verticalLayout_2.addWidget(self.pushButton_3)
|
self.verticalLayout_2.addWidget(self.pushButton_3)
|
||||||
|
@ -149,7 +149,7 @@ class Ui_Form(object):
|
||||||
self.label_7.setText(_translate("Form", "Stop Frequency:"))
|
self.label_7.setText(_translate("Form", "Stop Frequency:"))
|
||||||
self.label_5.setText(_translate("Form", "Start Frequency:"))
|
self.label_5.setText(_translate("Form", "Start Frequency:"))
|
||||||
self.startButton.setText(_translate("Form", "Start Sweep"))
|
self.startButton.setText(_translate("Form", "Start Sweep"))
|
||||||
self.pushButton.setText(_translate("Form", "Calibrate"))
|
self.calibrationButton.setText(_translate("Form", "Calibrate"))
|
||||||
self.pushButton_3.setText(_translate("Form", "T&M Settings"))
|
self.pushButton_3.setText(_translate("Form", "T&M Settings"))
|
||||||
self.titleinfoLabel.setText(_translate("Form", "Info Box:"))
|
self.titleinfoLabel.setText(_translate("Form", "Info Box:"))
|
||||||
from nqrduck.contrib.mplwidget import MplWidget
|
from nqrduck.contrib.mplwidget import MplWidget
|
||||||
|
|
Loading…
Reference in a new issue