From f45ca6a5742abeaa64bcf94fa2fa65bc7106ba0d Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 09:37:00 +0200 Subject: [PATCH 1/9] Added proper display and handling of serial connection. --- src/nqrduck_autotm/controller.py | 158 ++++++++++++++++++++++--------- src/nqrduck_autotm/model.py | 1 + src/nqrduck_autotm/view.py | 103 ++++++++++++++------ 3 files changed, 188 insertions(+), 74 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 04ddd48..8301d8e 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -24,17 +24,41 @@ class AutoTMController(ModuleController): for device in self.module.model.available_devices: logger.debug("Found device: %s", device) - def connect(self, device: str) -> None: - """Connect to the specified device. + def handle_connection(self, device: str) -> None: + """Connect or disconnect to the specified device based on if there already is a connection. Args: - device (str): The device port to connect to.""" + device (str): The device port to connect to. + + @TODO: If the user actually want to connect to another device while already connected to one, + this would have to be handled differently. But this doesn't really make sense in the current implementation. + """ logger.debug("Connecting to device %s", device) + # If the user has already connected to a device, close the previous connection + if self.module.model.serial is not None: + if self.module.model.serial.isOpen(): + logger.debug("Closing previous connection") + serial = self.module.model.serial + serial.close() + self.module.model.serial = serial + else: + self.open_connection(device) + # This is just for the first time the user connects to the device + else: + self.open_connection(device) + + def open_connection(self, device: str) -> None: + """Open a connection to the specified device. + + Args: + device (str): The device port to connect to. + """ try: - self.module.model.serial = QtSerialPort.QSerialPort( + serial = QtSerialPort.QSerialPort( device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read ) - self.module.model.serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite) + serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite) + self.module.model.serial = serial logger.debug("Connected to device %s", device) except Exception as e: @@ -77,9 +101,12 @@ class AutoTMController(ModuleController): return if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY: - error = "Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz" % ( - MIN_FREQUENCY / 1e6, - MAX_FREQUENCY / 1e6, + error = ( + "Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz" + % ( + MIN_FREQUENCY / 1e6, + MAX_FREQUENCY / 1e6, + ) ) logger.error(error) self.module.view.add_info_text(error) @@ -108,7 +135,10 @@ class AutoTMController(ModuleController): # logger.debug("Received data: %s", text) # If the text starts with 'f' and the frequency sweep spinner is visible we know that the data is a data point # then we have the data for the return loss and the phase at a certain frequency - if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible(): + if ( + text.startswith("f") + and self.module.view.frequency_sweep_spinner.isVisible() + ): text = text[1:].split("r") frequency = float(text[0]) return_loss, phase = map(float, text[1].split("p")) @@ -116,24 +146,38 @@ class AutoTMController(ModuleController): # If the text starts with 'r' and no calibration is active we know that the data is a measurement elif text.startswith("r") and self.module.model.active_calibration == None: logger.debug("Measurement finished") - self.module.model.measurement = S11Data(self.module.model.data_points.copy()) + self.module.model.measurement = S11Data( + self.module.model.data_points.copy() + ) self.module.view.frequency_sweep_spinner.hide() # If the text starts with 'r' and a short calibration is active we know that the data is a short calibration - elif text.startswith("r") and self.module.model.active_calibration == "short": + elif ( + text.startswith("r") and self.module.model.active_calibration == "short" + ): logger.debug("Short calibration finished") - self.module.model.short_calibration = S11Data(self.module.model.data_points.copy()) + self.module.model.short_calibration = S11Data( + self.module.model.data_points.copy() + ) self.module.model.active_calibration = None self.module.view.frequency_sweep_spinner.hide() # If the text starts with 'r' and an open calibration is active we know that the data is an open calibration - elif text.startswith("r") and self.module.model.active_calibration == "open": + elif ( + text.startswith("r") and self.module.model.active_calibration == "open" + ): logger.debug("Open calibration finished") - self.module.model.open_calibration = S11Data(self.module.model.data_points.copy()) + self.module.model.open_calibration = S11Data( + self.module.model.data_points.copy() + ) self.module.model.active_calibration = None self.module.view.frequency_sweep_spinner.hide() # If the text starts with 'r' and a load calibration is active we know that the data is a load calibration - elif text.startswith("r") and self.module.model.active_calibration == "load": + elif ( + text.startswith("r") and self.module.model.active_calibration == "load" + ): logger.debug("Load calibration finished") - self.module.model.load_calibration = S11Data(self.module.model.data_points.copy()) + self.module.model.load_calibration = S11Data( + self.module.model.data_points.copy() + ) self.module.model.active_calibration = None self.module.view.frequency_sweep_spinner.hide() # If the text starts with 'i' we know that the data is an info message @@ -167,7 +211,9 @@ class AutoTMController(ModuleController): logger.debug("Starting next voltage sweep: %s", command) self.send_command(command) - def on_short_calibration(self, start_frequency: float, stop_frequency: float) -> None: + 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. """ @@ -175,7 +221,9 @@ class AutoTMController(ModuleController): 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: + 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. """ @@ -183,7 +231,9 @@ class AutoTMController(ModuleController): 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: + 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. """ @@ -205,13 +255,19 @@ class AutoTMController(ModuleController): 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("Could not calculate calibration. No short calibration data points available.") + logger.error( + "Could not calculate calibration. No short calibration data points available." + ) return if self.module.model.open_calibration == None: - logger.error("Could not calculate calibration. No open calibration data points available.") + logger.error( + "Could not calculate calibration. No open calibration data points available." + ) return if self.module.model.load_calibration == None: - logger.error("Could not calculate calibration. No load calibration data points available.") + logger.error( + "Could not calculate calibration. No load calibration data points available." + ) return # Then we calculate the calibration @@ -226,7 +282,9 @@ class AutoTMController(ModuleController): e_00s = [] e_11s = [] delta_es = [] - for gamma_s, gamma_o, gamma_l in zip(measured_gamma_short, measured_gamma_open, measured_gamma_load): + for gamma_s, gamma_o, gamma_l in zip( + measured_gamma_short, measured_gamma_open, measured_gamma_load + ): # This is the solution from A = np.array( [ @@ -257,15 +315,21 @@ class AutoTMController(ModuleController): logger.debug("Exporting calibration") # First we check if the short and open calibration data points are available if self.module.model.short_calibration == None: - logger.error("Could not export calibration. No short calibration data points available.") + logger.error( + "Could not export calibration. No short calibration data points available." + ) return if self.module.model.open_calibration == None: - logger.error("Could not export calibration. No open calibration data points available.") + logger.error( + "Could not export calibration. No open calibration data points available." + ) return if self.module.model.load_calibration == None: - logger.error("Could not export calibration. No load calibration data points available.") + logger.error( + "Could not export calibration. No load calibration data points available." + ) return # Then we export the different calibrations as a json file @@ -316,7 +380,9 @@ class AutoTMController(ModuleController): return if tuning_voltage < 0 or matching_voltage < 0: - error = "Could not set voltages. Tuning and matching voltage must be positive" + error = ( + "Could not set voltages. Tuning and matching voltage must be positive" + ) logger.error(error) self.module.view.add_info_text(error) return @@ -332,7 +398,7 @@ class AutoTMController(ModuleController): tuning_voltage, matching_voltage, ) - + command = "v%sv%s" % (matching_voltage, tuning_voltage) self.send_command(command) @@ -368,7 +434,12 @@ class AutoTMController(ModuleController): self.module.view.add_info_text(error) return - if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0 or voltage_resolution < 0: + if ( + start_frequency < 0 + or stop_frequency < 0 + or frequency_step < 0 + or voltage_resolution < 0 + ): error = "Could not generate LUT. Start frequency, stop frequency, frequency step and voltage resolution must be positive" logger.error(error) self.module.view.add_info_text(error) @@ -395,7 +466,9 @@ class AutoTMController(ModuleController): ) # We create the lookup table - LUT = LookupTable(start_frequency, stop_frequency, frequency_step, voltage_resolution) + LUT = LookupTable( + start_frequency, stop_frequency, frequency_step, voltage_resolution + ) LUT.started_frequency = start_frequency self.module.model.LUT = LUT @@ -403,25 +476,24 @@ class AutoTMController(ModuleController): # We write the first command to the serial connection command = "s%s" % (start_frequency) self.send_command(command) - + def switch_to_preamp(self) -> None: - """ This method is used to send the command 'cp' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'Preamp'. + """This method is used to send the command 'cp' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'Preamp'. This is the mode for either NQR or NMR measurements or if on wants to check the tuning of the probe coil on a network analyzer. """ logger.debug("Switching to preamp") self.send_command("cp") - - + def switch_to_atm(self) -> None: - """ This method is used to send the command 'ca' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'ATM. + """This method is used to send the command 'ca' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'ATM. In this state the atm system can be used to measure the reflection coefficient of the probecoils. """ logger.debug("Switching to atm") self.send_command("ca") - def send_command(self, command : str) -> None: - """ This method is used to send a command to the active serial connection. - + def send_command(self, command: str) -> None: + """This method is used to send a command to the active serial connection. + Args: command (str): The command that should be send to the atm system. """ @@ -434,13 +506,13 @@ class AutoTMController(ModuleController): QApplication.processEvents() except AttributeError: logger.error("Could not send command. No device connected.") - self.module.view.add_error_text("Could not send command. No device connected.") - + self.module.view.add_error_text( + "Could not send command. No device connected." + ) + def homing(self) -> None: - """ This method is used to send the command 'h' to the atm system. + """This method is used to send the command 'h' to the atm system. This command is used to home the stepper motors of the atm system. """ logger.debug("Homing") self.send_command("h") - - diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index cfda5e8..b3ac602 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -148,6 +148,7 @@ class AutoTMModel(ModuleModel): self.data_points = [] self.active_calibration = None self.calibration = None + self.serial = None @property def available_devices(self): diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 2160193..752d392 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -34,7 +34,7 @@ class AutoTMView(ModuleView): self._ui_form = Ui_Form() self._ui_form.setupUi(self) self.widget = widget - + self.frequency_sweep_spinner = self.FrequencySweepSpinner() self.frequency_sweep_spinner.hide() @@ -45,7 +45,9 @@ class AutoTMView(ModuleView): self._ui_form.refreshButton.clicked.connect(self.module.controller.find_devices) # Connect the available devices changed signal to the on_available_devices_changed slot - self.module.model.available_devices_changed.connect(self.on_available_devices_changed) + self.module.model.available_devices_changed.connect( + self.on_available_devices_changed + ) # Connect the serial changed signal to the on_serial_changed slot self.module.model.serial_changed.connect(self.on_serial_changed) @@ -81,14 +83,20 @@ class AutoTMView(ModuleView): ) # On clicking of the calibration button call the on_calibration_button_clicked method - self._ui_form.calibrationButton.clicked.connect(self.on_calibration_button_clicked) - + self._ui_form.calibrationButton.clicked.connect( + self.on_calibration_button_clicked + ) + # On clicking of the switchpreampButton call the switch_preamp method - self._ui_form.switchpreampButton.clicked.connect(self.module.controller.switch_to_preamp) - + self._ui_form.switchpreampButton.clicked.connect( + self.module.controller.switch_to_preamp + ) + # On clicking of the switchATMButton call the switch_atm method - self._ui_form.switchATMButton.clicked.connect(self.module.controller.switch_to_atm) - + self._ui_form.switchATMButton.clicked.connect( + self.module.controller.switch_to_atm + ) + # On clicking of the homingButton call the homing method self._ui_form.homingButton.clicked.connect(self.module.controller.homing) @@ -97,7 +105,9 @@ class AutoTMView(ModuleView): # Add a vertical layout to the info box self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout()) - self._ui_form.scrollAreaWidgetContents.layout().setAlignment(Qt.AlignmentFlag.AlignTop) + self._ui_form.scrollAreaWidgetContents.layout().setAlignment( + Qt.AlignmentFlag.AlignTop + ) self.init_plot() self.init_labels() @@ -119,7 +129,7 @@ class AutoTMView(ModuleView): ax.set_xlim(0, 100) ax.set_ylim(-100, 0) self._ui_form.S11Plot.canvas.draw() - + self.phase_ax = self._ui_form.S11Plot.canvas.ax.twinx() def on_calibration_button_clicked(self) -> None: @@ -150,7 +160,7 @@ class AutoTMView(ModuleView): """ logger.debug("Connect button clicked") selected_device = self._ui_form.portBox.currentText() - self.module.controller.connect(selected_device) + self.module.controller.handle_connection(selected_device) @pyqtSlot(QSerialPort) def on_serial_changed(self, serial: QSerialPort) -> None: @@ -159,11 +169,16 @@ class AutoTMView(ModuleView): Args: serial (serial.Serial): The current serial connection.""" logger.debug("Updating serial connection label") - if serial: + if serial.isOpen(): self._ui_form.connectionLabel.setText(serial.portName()) self.add_info_text("Connected to device %s" % serial.portName()) + # Change the connectButton to a disconnectButton + self._ui_form.connectButton.setText("Disconnect") else: self._ui_form.connectionLabel.setText("Disconnected") + self.add_info_text("Disconnected from device") + self._ui_form.connectButton.setText("Connect") + logger.debug("Updated serial connection label") def plot_measurement(self, data: "S11Data") -> None: @@ -181,7 +196,7 @@ class AutoTMView(ModuleView): gamma = data.gamma self._ui_form.S11Plot.canvas.ax.clear() - + magnitude_ax = self._ui_form.S11Plot.canvas.ax magnitude_ax.clear() @@ -190,20 +205,22 @@ class AutoTMView(ModuleView): # Calibration for visualization happens here. if self.module.model.calibration is not None: - calibration = self.module.model.calibration e_00 = calibration[0] e11 = calibration[1] delta_e = calibration[2] gamma_corr = [ - (data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(gamma) + (data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) + for i, data_point in enumerate(gamma) + ] + + return_loss_db_corr = [ + -20 * cmath.log10(abs(g + 1e-12)) for g in gamma_corr ] - - return_loss_db_corr = [-20 * cmath.log10(abs(g + 1e-12)) for g in gamma_corr] magnitude_ax.plot(frequency, return_loss_db_corr, color="red") - - else: + + else: magnitude_ax.plot(frequency, return_loss_db, color="blue") self.phase_ax.set_ylabel("|Phase (deg)|") @@ -236,7 +253,9 @@ class AutoTMView(ModuleView): text_label = QLabel(text) text_label.setStyleSheet("font-size: 25px;") 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 add_error_text(self, text: str) -> None: """Adds text to the error text box. @@ -250,7 +269,9 @@ class AutoTMView(ModuleView): text_label = QLabel(text) text_label.setStyleSheet("font-size: 25px; color: red;") 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.""" @@ -296,13 +317,19 @@ class AutoTMView(ModuleView): # Create table widget self.table_widget = QTableWidget() self.table_widget.setColumnCount(3) - self.table_widget.setHorizontalHeaderLabels(["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"]) + self.table_widget.setHorizontalHeaderLabels( + ["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"] + ) LUT = self.module.model.LUT for row, frequency in enumerate(LUT.data.keys()): self.table_widget.insertRow(row) self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency))) - self.table_widget.setItem(row, 1, QTableWidgetItem(str(LUT.data[frequency][0]))) - self.table_widget.setItem(row, 2, QTableWidgetItem(str(LUT.data[frequency][1]))) + self.table_widget.setItem( + row, 1, QTableWidgetItem(str(LUT.data[frequency][0])) + ) + self.table_widget.setItem( + row, 2, QTableWidgetItem(str(LUT.data[frequency][1])) + ) # Add table widget to main layout main_layout.addWidget(self.table_widget) @@ -355,7 +382,9 @@ class AutoTMView(ModuleView): short_layout = QVBoxLayout() short_button = QPushButton("Short") short_button.clicked.connect( - lambda: self.module.controller.on_short_calibration(start_edit.text(), stop_edit.text()) + lambda: self.module.controller.on_short_calibration( + start_edit.text(), stop_edit.text() + ) ) # Short plot widget self.short_plot = MplWidget() @@ -367,7 +396,9 @@ class AutoTMView(ModuleView): open_layout = QVBoxLayout() open_button = QPushButton("Open") open_button.clicked.connect( - lambda: self.module.controller.on_open_calibration(start_edit.text(), stop_edit.text()) + lambda: self.module.controller.on_open_calibration( + start_edit.text(), stop_edit.text() + ) ) # Open plot widget self.open_plot = MplWidget() @@ -379,7 +410,9 @@ class AutoTMView(ModuleView): load_layout = QVBoxLayout() load_button = QPushButton("Load") load_button.clicked.connect( - lambda: self.module.controller.on_load_calibration(start_edit.text(), stop_edit.text()) + lambda: self.module.controller.on_load_calibration( + start_edit.text(), stop_edit.text() + ) ) # Load plot widget self.load_plot = MplWidget() @@ -407,9 +440,15 @@ class AutoTMView(ModuleView): 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) + 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: "S11Data") -> None: self.on_calibration_finished("short", self.short_plot, short_calibration) @@ -420,7 +459,9 @@ class AutoTMView(ModuleView): def on_load_calibration_finished(self, load_calibration: "S11Data") -> None: self.on_calibration_finished("load", self.load_plot, load_calibration) - def on_calibration_finished(self, type: str, widget: MplWidget, data: "S11Data") -> None: + def on_calibration_finished( + self, type: str, widget: MplWidget, data: "S11Data" + ) -> None: """This method is called when a calibration has finished. It plots the calibration data on the given widget. """ From bb585141f123497fb311363829196de7dd99cee8 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 10:17:30 +0200 Subject: [PATCH 2/9] Added serial connection timeout and connection checks. Improved visualization of faulty connection settings. --- src/nqrduck_autotm/controller.py | 54 ++++++++++++++++++++++++++------ src/nqrduck_autotm/view.py | 20 ++++++++++-- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 8301d8e..81fa550 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -491,24 +491,58 @@ class AutoTMController(ModuleController): logger.debug("Switching to atm") self.send_command("ca") - def send_command(self, command: str) -> None: + def send_command(self, command: str) -> bool: """This method is used to send a command to the active serial connection. Args: command (str): The command that should be send to the atm system. + + Returns: + bool: True if the command was send successfully, False otherwise. """ logger.debug("Sending command %s", command) + timeout = 1000 # ms + + if self.module.model.serial is None: + logger.error("Could not send command. No serial connection") + self.module.view.add_error_text( + "Could not send command. No serial connection" + ) + return False + + if self.module.model.serial.isOpen() == False: + logger.error("Could not send command. Serial connection is not open") + self.module.view.add_error_text( + "Could not send command. Serial connection is not open" + ) + return False + try: self.module.model.serial.write(command.encode("utf-8")) - # Wait for 0.5 seconds - QTest.qWait(500) - # Make sure that the command is being send - QApplication.processEvents() - except AttributeError: - logger.error("Could not send command. No device connected.") - self.module.view.add_error_text( - "Could not send command. No device connected." - ) + # Wait for the confirmation of the command ('c') to be read with a timeout of 1 second + + if not self.module.model.serial.waitForReadyRead(timeout): + logger.error("Could not send command. Timeout") + self.module.view.add_error_text("Could not send command. Timeout") + return False + + # Read the confirmation of the command + confirmation = self.module.model.serial.readAll().data().decode("utf-8") + logger.debug("Confirmation: %s", confirmation) + + if confirmation == "c": + logger.debug("Command send successfully") + return True + else: + logger.error("Could not send command. No confirmation received") + self.module.view.add_error_text( + "Could not send command. No confirmation received" + ) + return False + + except Exception as e: + logger.error("Could not send command. %s", e) + self.module.view.add_error_text("Could not send command. %s" % e) def homing(self) -> None: """This method is used to send the command 'h' to the atm system. diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 752d392..bd92bb5 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -21,6 +21,7 @@ from PyQt6.QtWidgets import ( from PyQt6.QtCore import pyqtSlot, Qt from nqrduck.module.module_view import ModuleView from nqrduck.contrib.mplwidget import MplWidget +from nqrduck.assets.icons import Logos from .widget import Ui_Form logger = logging.getLogger(__name__) @@ -109,6 +110,10 @@ class AutoTMView(ModuleView): Qt.AlignmentFlag.AlignTop ) + # Add button Icons + self._ui_form.startButton.setIcon(Logos.Play_16x16()) + self._ui_form.startButton.setIconSize(self._ui_form.startButton.size()) + self.init_plot() self.init_labels() @@ -263,12 +268,23 @@ class AutoTMView(ModuleView): Args: text (str): Text to add to the error text box. """ + message_widget = QWidget() + message_widget.setLayout(QHBoxLayout()) + + error_icon = QLabel() + error_icon.setPixmap( + Logos.Error_16x16().pixmap(Logos.Error_16x16().availableSizes()[0]) + ) # Add a timestamp to the text timestamp = datetime.now().strftime("%H:%M:%S") - text = "[%s] %s ERROR:" % (timestamp, text) + text = "[%s] %s" % (timestamp, text) text_label = QLabel(text) text_label.setStyleSheet("font-size: 25px; color: red;") - self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label) + + message_widget.layout().addWidget(error_icon) + message_widget.layout().addWidget(text_label) + + self._ui_form.scrollAreaWidgetContents.layout().addWidget(message_widget) self._ui_form.scrollArea.verticalScrollBar().setValue( self._ui_form.scrollArea.verticalScrollBar().maximum() ) From 75aa72eaf42cd1b9d299c9cdc4c6cf4ceeaae921 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 10:43:06 +0200 Subject: [PATCH 3/9] Updated loading animation --- src/nqrduck_autotm/view.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index bd92bb5..06e0d5a 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -22,6 +22,7 @@ from PyQt6.QtCore import pyqtSlot, Qt from nqrduck.module.module_view import ModuleView from nqrduck.contrib.mplwidget import MplWidget from nqrduck.assets.icons import Logos +from nqrduck.assets.animations import DuckAnimations from .widget import Ui_Form logger = logging.getLogger(__name__) @@ -36,7 +37,7 @@ class AutoTMView(ModuleView): self._ui_form.setupUi(self) self.widget = widget - self.frequency_sweep_spinner = self.FrequencySweepSpinner() + self.frequency_sweep_spinner = self.FrequencySweepSpinner(self) self.frequency_sweep_spinner.hide() # Disable the connectButton while no devices are selected @@ -291,7 +292,7 @@ class AutoTMView(ModuleView): def create_frequency_sweep_spinner_dialog(self) -> None: """Creates a frequency sweep spinner dialog.""" - self.frequency_sweep_spinner = self.FrequencySweepSpinner() + self.frequency_sweep_spinner = self.FrequencySweepSpinner(self) self.frequency_sweep_spinner.show() def view_lut(self) -> None: @@ -303,19 +304,19 @@ class AutoTMView(ModuleView): class FrequencySweepSpinner(QDialog): """This class implements a spinner dialog that is shown during a frequency sweep.""" - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent) 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_movie = DuckAnimations.DuckSleep128x128() self.spinner_label = QLabel(self) self.spinner_label.setMovie(self.spinner_movie) self.layout = QVBoxLayout(self) + self.layout.addWidget(QLabel("Performing frequency sweep...")) self.layout.addWidget(self.spinner_label) self.spinner_movie.start() From 3879bb8f1572f17e6273e0b6008ca51023b1c68d Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 10:46:08 +0200 Subject: [PATCH 4/9] Fixed connection check for frequency sweep. --- src/nqrduck_autotm/controller.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 81fa550..44ccea4 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -119,12 +119,14 @@ class AutoTMController(ModuleController): stop_frequency, frequency_step, ) - # We create the frequency sweep spinner dialog - self.module.model.clear_data_points() - self.module.view.create_frequency_sweep_spinner_dialog() + # Print the command 'fff' to the serial connection command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step) - self.send_command(command) + confirmation = self.send_command(command) + if confirmation: + # We create the frequency sweep spinner dialog + self.module.model.clear_data_points() + self.module.view.create_frequency_sweep_spinner_dialog() def on_ready_read(self) -> None: """This method is called when data is received from the serial connection.""" @@ -475,7 +477,9 @@ class AutoTMController(ModuleController): # We write the first command to the serial connection command = "s%s" % (start_frequency) - self.send_command(command) + confirmation = self.send_command(command) + if not confirmation: + return def switch_to_preamp(self) -> None: """This method is used to send the command 'cp' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'Preamp'. @@ -501,7 +505,7 @@ class AutoTMController(ModuleController): bool: True if the command was send successfully, False otherwise. """ logger.debug("Sending command %s", command) - timeout = 1000 # ms + timeout = 10000 # ms if self.module.model.serial is None: logger.error("Could not send command. No serial connection") @@ -526,8 +530,7 @@ class AutoTMController(ModuleController): self.module.view.add_error_text("Could not send command. Timeout") return False - # Read the confirmation of the command - confirmation = self.module.model.serial.readAll().data().decode("utf-8") + confirmation = self.module.model.serial.readLine().data().decode("utf-8") logger.debug("Confirmation: %s", confirmation) if confirmation == "c": From 8579151bc28e956912585032b5b73d9674118536 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 10:52:08 +0200 Subject: [PATCH 5/9] Added timing for frequency sweep. --- src/nqrduck_autotm/controller.py | 10 ++++++++++ src/nqrduck_autotm/model.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 44ccea4..279c7b2 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -1,6 +1,7 @@ import logging import numpy as np import json +import time from serial.tools.list_ports import comports from PyQt6.QtTest import QTest from PyQt6 import QtSerialPort @@ -122,6 +123,7 @@ class AutoTMController(ModuleController): # Print the command 'fff' to the serial connection command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step) + self.module.model.frequency_sweep_start = time.time() confirmation = self.send_command(command) if confirmation: # We create the frequency sweep spinner dialog @@ -152,6 +154,14 @@ class AutoTMController(ModuleController): self.module.model.data_points.copy() ) self.module.view.frequency_sweep_spinner.hide() + self.module.model.frequency_sweep_stop = time.time() + self.module.view.add_info_text( + "Frequency sweep finished in %.2f seconds" + % ( + self.module.model.frequency_sweep_stop + - self.module.model.frequency_sweep_start + ) + ) # If the text starts with 'r' and a short calibration is active we know that the data is a short calibration elif ( text.startswith("r") and self.module.model.active_calibration == "short" diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index b3ac602..dcad2f2 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -266,3 +266,21 @@ class AutoTMModel(ModuleModel): @LUT.setter def LUT(self, value): self._LUT = value + + @property + def frequency_sweep_start(self): + """The timestamp for when the frequency sweep has been started. This is used for timing of the frequency sweep.""" + return self._frequency_sweep_start + + @frequency_sweep_start.setter + def frequency_sweep_start(self, value): + self._frequency_sweep_start = value + + @property + def frequency_sweep_end(self): + """The timestamp for when the frequency sweep has been ended. This is used for timing of the frequency sweep.""" + return self._frequency_sweep_end + + @frequency_sweep_end.setter + def frequency_sweep_end(self, value): + self._frequency_sweep_end = value From b0ddbae9c54c1b8b2c33aab6d8bf1d7c8559ea9f Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 11:40:43 +0200 Subject: [PATCH 6/9] Updated widget. --- src/nqrduck_autotm/resources/autotm_widget.ui | 118 +++++++++++++++++- src/nqrduck_autotm/view.py | 2 +- src/nqrduck_autotm/widget.py | 62 ++++++++- 3 files changed, 172 insertions(+), 10 deletions(-) diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 2869e08..2b2838b 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -97,14 +97,114 @@ Mechanical - + - + + + + + Home + + + + + + + Step Size: + + + + + + + -1000 + + + 1000 + + + 500 + + + + + + + Tuning Stepper: + + + + + + + - + + + + + + + + + + + + + + + Matching Stepper: + + + + + + + - + + + + + + + + + + + + + + + Home + + + + + + + Stepper Control: + + + + + + + - Homing + Start Position + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -257,7 +357,11 @@ - + + + 80 + + @@ -267,7 +371,11 @@ - + + + 100 + + diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 06e0d5a..8aa6707 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -100,7 +100,7 @@ class AutoTMView(ModuleView): ) # On clicking of the homingButton call the homing method - self._ui_form.homingButton.clicked.connect(self.module.controller.homing) + self._ui_form.starpositionButton.clicked.connect(self.module.controller.homing) # Connect the measurement finished signal to the plot_measurement slot self.module.model.measurement_finished.connect(self.plot_measurement) diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index b8e3758..d0a4ac4 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -62,9 +62,51 @@ class Ui_Form(object): self.mechTab.setObjectName("mechTab") self.verticalLayout = QtWidgets.QVBoxLayout(self.mechTab) self.verticalLayout.setObjectName("verticalLayout") - self.homingButton = QtWidgets.QPushButton(parent=self.mechTab) - self.homingButton.setObjectName("homingButton") - self.verticalLayout.addWidget(self.homingButton) + self.gridLayout_4 = QtWidgets.QGridLayout() + self.gridLayout_4.setObjectName("gridLayout_4") + self.homematcherButton = QtWidgets.QPushButton(parent=self.mechTab) + self.homematcherButton.setObjectName("homematcherButton") + self.gridLayout_4.addWidget(self.homematcherButton, 5, 1, 1, 1) + self.label_17 = QtWidgets.QLabel(parent=self.mechTab) + self.label_17.setObjectName("label_17") + self.gridLayout_4.addWidget(self.label_17, 1, 0, 1, 1) + self.stepsizeBox = QtWidgets.QSpinBox(parent=self.mechTab) + self.stepsizeBox.setMinimum(-1000) + self.stepsizeBox.setMaximum(1000) + self.stepsizeBox.setProperty("value", 500) + self.stepsizeBox.setObjectName("stepsizeBox") + self.gridLayout_4.addWidget(self.stepsizeBox, 1, 1, 1, 1) + self.label_18 = QtWidgets.QLabel(parent=self.mechTab) + self.label_18.setObjectName("label_18") + self.gridLayout_4.addWidget(self.label_18, 2, 0, 1, 3) + self.decreasetunerButton = QtWidgets.QPushButton(parent=self.mechTab) + self.decreasetunerButton.setObjectName("decreasetunerButton") + self.gridLayout_4.addWidget(self.decreasetunerButton, 3, 0, 1, 1) + self.increasetunerButton = QtWidgets.QPushButton(parent=self.mechTab) + self.increasetunerButton.setObjectName("increasetunerButton") + self.gridLayout_4.addWidget(self.increasetunerButton, 3, 2, 1, 1) + self.label_19 = QtWidgets.QLabel(parent=self.mechTab) + self.label_19.setObjectName("label_19") + self.gridLayout_4.addWidget(self.label_19, 4, 0, 1, 3) + self.decreasematcherButton = QtWidgets.QPushButton(parent=self.mechTab) + self.decreasematcherButton.setObjectName("decreasematcherButton") + self.gridLayout_4.addWidget(self.decreasematcherButton, 5, 0, 1, 1) + self.increasematcherButton = QtWidgets.QPushButton(parent=self.mechTab) + self.increasematcherButton.setObjectName("increasematcherButton") + self.gridLayout_4.addWidget(self.increasematcherButton, 5, 2, 1, 1) + self.hometunerButton = QtWidgets.QPushButton(parent=self.mechTab) + self.hometunerButton.setObjectName("hometunerButton") + self.gridLayout_4.addWidget(self.hometunerButton, 3, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(parent=self.mechTab) + self.label_16.setObjectName("label_16") + self.gridLayout_4.addWidget(self.label_16, 0, 0, 1, 3) + self.verticalLayout.addLayout(self.gridLayout_4) + self.starpositionButton = QtWidgets.QPushButton(parent=self.mechTab) + self.starpositionButton.setObjectName("starpositionButton") + self.verticalLayout.addWidget(self.starpositionButton) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.verticalLayout.setStretch(1, 1) self.typeTab.addTab(self.mechTab, "") self.elecTab = QtWidgets.QWidget() self.elecTab.setObjectName("elecTab") @@ -215,7 +257,17 @@ class Ui_Form(object): self.label_10.setText(_translate("Form", "Connected to:")) self.connectButton.setText(_translate("Form", "Connect")) self.titletypeLabel.setText(_translate("Form", "T&M Type:")) - self.homingButton.setText(_translate("Form", "Homing")) + self.homematcherButton.setText(_translate("Form", "Home")) + self.label_17.setText(_translate("Form", "Step Size:")) + self.label_18.setText(_translate("Form", "Tuning Stepper:")) + self.decreasetunerButton.setText(_translate("Form", "-")) + self.increasetunerButton.setText(_translate("Form", "+")) + self.label_19.setText(_translate("Form", "Matching Stepper:")) + self.decreasematcherButton.setText(_translate("Form", "-")) + self.increasematcherButton.setText(_translate("Form", "+")) + self.hometunerButton.setText(_translate("Form", "Home")) + self.label_16.setText(_translate("Form", "Stepper Control:")) + self.starpositionButton.setText(_translate("Form", "Start Position")) self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical")) self.label_4.setText(_translate("Form", "Voltage Resolution")) self.label_2.setText(_translate("Form", "Voltage Tuning")) @@ -233,7 +285,9 @@ class Ui_Form(object): self.switchATMButton.setText(_translate("Form", "ATM")) self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical")) self.titlefrequencyLabel.setText(_translate("Form", "Frequency Sweep:")) + self.startEdit.setText(_translate("Form", "80")) self.label_8.setText(_translate("Form", "MHz")) + self.stopEdit.setText(_translate("Form", "100")) self.label_6.setText(_translate("Form", "MHz")) self.label_7.setText(_translate("Form", "Stop Frequency:")) self.label_5.setText(_translate("Form", "Start Frequency:")) From a6df6da1d567b961199139ac586036e43ea62e8f Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 21 Aug 2023 11:49:58 +0200 Subject: [PATCH 7/9] Fixed calibration window view for wayland. --- src/nqrduck_autotm/view.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 8aa6707..df9d9a7 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -143,7 +143,7 @@ class AutoTMView(ModuleView): It opens the calibration window. """ logger.debug("Calibration button clicked") - self.calibration_window = self.CalibrationWindow(self.module) + self.calibration_window = self.CalibrationWindow(self.module, self) self.calibration_window.show() @pyqtSlot(list) @@ -367,11 +367,11 @@ class AutoTMView(ModuleView): matching_voltage = str(self.module.model.LUT.data[frequency][0]) self.module.controller.set_voltages(tuning_voltage, matching_voltage) - class CalibrationWindow(QWidget): + class CalibrationWindow(QDialog): def __init__(self, module, parent=None): - super().__init__() - self.module = module + super().__init__(parent) self.setParent(parent) + self.module = module self.setWindowTitle("Calibration") # Add vertical main layout @@ -390,6 +390,7 @@ class AutoTMView(ModuleView): frequency_layout.addWidget(stop_edit) unit_label = QLabel("MHz") frequency_layout.addWidget(unit_label) + frequency_layout.addStretch() # Add horizontal layout for the calibration type type_layout = QHBoxLayout() From b6676f709f5a012715ec5e0c5290fa52472f9baa Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 23 Aug 2023 08:54:57 +0200 Subject: [PATCH 8/9] Fixed units of phase measurement. --- src/nqrduck_autotm/model.py | 2 +- src/nqrduck_autotm/view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index dcad2f2..7e7b8f0 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -12,7 +12,7 @@ class S11Data: # Conversion factors - the data is generally sent and received in mV # These values are used to convert the data to dB and degrees CENTER_POINT_MAGNITUDE = 900 # mV - CENTER_POINT_PHASE = 900 # mV + CENTER_POINT_PHASE = 0 # mV MAGNITUDE_SLOPE = 30 # dB/mV PHASE_SLOPE = 10 # deg/mV diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index df9d9a7..8c15319 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -231,7 +231,7 @@ class AutoTMView(ModuleView): self.phase_ax.set_ylabel("|Phase (deg)|") self.phase_ax.plot(frequency, phase, color="orange", linestyle="--") - self.phase_ax.set_ylim(-180, 180) + self.phase_ax.set_ylim(0, 180) self.phase_ax.invert_yaxis() magnitude_ax.set_xlabel("Frequency (MHz)") From c21dc155fa5b50d6dce64605a6d86007ea8086c7 Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 23 Aug 2023 10:06:49 +0200 Subject: [PATCH 9/9] Implemented phase correction. Now the calibration works :). --- src/nqrduck_autotm/model.py | 96 +++++++++++++++++++++++++++++++++++-- src/nqrduck_autotm/view.py | 3 +- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 7e7b8f0..8ecc223 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -1,6 +1,7 @@ import cmath import numpy as np import logging +from scipy.signal import find_peaks from PyQt6.QtCore import pyqtSignal from PyQt6.QtSerialPort import QSerialPort from nqrduck.module.module_model import ModuleModel @@ -32,9 +33,18 @@ class S11Data: ) / self.MAGNITUDE_SLOPE @property - def phase_deg(self): - """Returns the absolute value of the phase in degrees""" - return (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE + def phase_deg(self, phase_correction=True): + """Returns the absolute value of the phase in degrees + + Keyword Arguments: + phase_correction {bool} -- If True, the phase correction is applied. (default: {False}) + """ + + phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE + if phase_correction: + phase_deg = self.phase_correction(self.frequency, phase_deg) + + return phase_deg @property def phase_rad(self): @@ -51,6 +61,86 @@ class S11Data: for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad) ] + def phase_correction( + self, frequency_data: np.array, phase_data: np.array + ) -> np.array: + """This method fixes the phase sign of the phase data. + The AD8302 can only measure the absolute value of the phase. + Therefore we need to correct the phase sign. This can be done via the slope of the phase. + If the slope is negative, the phase is positive and vice versa. + + Args: + frequency_data (np.array): The frequency data. + phase_data (np.array): The phase data. + + Returns: + np.array: The corrected phase data. + """ + # First we apply a moving average filter to the phase data + WINDOW_SIZE = 5 + phase_data_filtered = ( + np.convolve(phase_data, np.ones(WINDOW_SIZE), "same") / WINDOW_SIZE + ) + + # Fix transient response + phase_data_filtered[: WINDOW_SIZE // 2] = phase_data[: WINDOW_SIZE // 2] + phase_data_filtered[-WINDOW_SIZE // 2 :] = phase_data[-WINDOW_SIZE // 2 :] + + # Now we find the peaks and valleys of the data + HEIGHT = 100 + distance = len(phase_data_filtered) / 10 + + peaks, _ = find_peaks(phase_data_filtered, distance=distance, height=HEIGHT) + + valleys, _ = find_peaks( + 180 - phase_data_filtered, distance=distance, height=HEIGHT + ) + + # Determine if the first point is a peak or a valley + if phase_data_filtered[0] > phase_data_filtered[1]: + peaks = np.insert(peaks, 0, 0) + else: + valleys = np.insert(valleys, 0, 0) + + # Determine if the last point is a peak or a valley + if phase_data_filtered[-1] > phase_data_filtered[-2]: + peaks = np.append(peaks, len(phase_data_filtered) - 1) + else: + valleys = np.append(valleys, len(phase_data_filtered) - 1) + + frequency_peaks = frequency_data[peaks] + frequency_valleys = frequency_data[valleys] + + # Combine the peaks and valleys + frequency_peaks_valleys = np.sort( + np.concatenate((frequency_peaks, frequency_valleys)) + ) + peaks_valleys = np.sort(np.concatenate((peaks, valleys))) + + # Now we can determine the slope of the phase + # For this we compare the phase of our peaks_valleys array to the next point + # If the phase is increasing, the slope is positive, if it is decreasing, the slope is negative + phase_slope = np.zeros(len(peaks_valleys) - 1) + for i in range(len(peaks_valleys) - 1): + phase_slope[i] = ( + phase_data_filtered[peaks_valleys[i + 1]] + - phase_data_filtered[peaks_valleys[i]] + ) + + # Now we can determine the sign of the phase + # If the slope is negative, the phase is positive and vice versa + phase_sign = np.sign(phase_slope) * -1 + + # Now we can correct the phase for the different sections + phase_data_corrected = np.zeros(len(phase_data)) + for i in range(len(peaks_valleys) - 1): + phase_data_corrected[peaks_valleys[i] : peaks_valleys[i + 1]] = ( + phase_data_filtered[peaks_valleys[i] : peaks_valleys[i + 1]] + * phase_sign[i] + ) + + return phase_data_corrected + def to_json(self): return { "frequency": self.frequency.tolist(), diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 8c15319..b04cef0 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -231,8 +231,7 @@ class AutoTMView(ModuleView): self.phase_ax.set_ylabel("|Phase (deg)|") self.phase_ax.plot(frequency, phase, color="orange", linestyle="--") - self.phase_ax.set_ylim(0, 180) - self.phase_ax.invert_yaxis() + # self.phase_ax.invert_yaxis() magnitude_ax.set_xlabel("Frequency (MHz)") magnitude_ax.set_ylabel("S11 (dB)")