From bfbc20c2e9ee93592cc165b2d49b12f7f9caa5aa Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 8 May 2024 16:26:57 +0200 Subject: [PATCH] Linting. --- src/nqrduck_autotm/__init__.py | 4 +- src/nqrduck_autotm/autotm.py | 4 +- src/nqrduck_autotm/controller.py | 500 ++++++++++++++++++++++--------- src/nqrduck_autotm/model.py | 124 ++++++-- src/nqrduck_autotm/view.py | 136 +++++++-- 5 files changed, 556 insertions(+), 212 deletions(-) diff --git a/src/nqrduck_autotm/__init__.py b/src/nqrduck_autotm/__init__.py index ee58352..c284dd2 100644 --- a/src/nqrduck_autotm/__init__.py +++ b/src/nqrduck_autotm/__init__.py @@ -1 +1,3 @@ -from .autotm import AutoTM as Module \ No newline at end of file +"""The NQRduck AutoTM module. It is used to automatically tune and match magnetic resonance probe coils.""" + +from .autotm import AutoTM as Module diff --git a/src/nqrduck_autotm/autotm.py b/src/nqrduck_autotm/autotm.py index 9f4781b..528e782 100644 --- a/src/nqrduck_autotm/autotm.py +++ b/src/nqrduck_autotm/autotm.py @@ -1,6 +1,8 @@ +"""The Module creation snippet for the NQRduck AutoTM module.""" + from nqrduck.module.module import Module from .model import AutoTMModel from .view import AutoTMView from .controller import AutoTMController -AutoTM = Module(AutoTMModel, AutoTMView, AutoTMController) \ No newline at end of file +AutoTM = Module(AutoTMModel, AutoTMView, AutoTMController) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 7d80d23..f9de20c 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -1,3 +1,5 @@ +"""The controller for the NQRduck AutoTM module.""" + import logging import time import numpy as np @@ -8,47 +10,75 @@ from PyQt6.QtCore import pyqtSlot from PyQt6.QtCore import QTimer from PyQt6.QtWidgets import QApplication from nqrduck.module.module_controller import ModuleController -from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition, Stepper +from .model import ( + S11Data, + ElectricalLookupTable, + MechanicalLookupTable, + SavedPosition, + Stepper, +) logger = logging.getLogger(__name__) class AutoTMController(ModuleController): + """The controller for the NQRduck AutoTM module. It handles the serial connection and the signals and slots.""" + BAUDRATE = 115200 - def on_loading(self): + def on_loading(self) -> None: """This method is called when the module is loaded. + It sets up the serial connection and connects the signals and slots. """ logger.debug("Setting up serial connection") self.find_devices() # Connect signals - self.module.model.serial_data_received.connect(self.process_frequency_sweep_data) + self.module.model.serial_data_received.connect( + self.process_frequency_sweep_data + ) self.module.model.serial_data_received.connect(self.process_measurement_data) self.module.model.serial_data_received.connect(self.process_calibration_data) - self.module.model.serial_data_received.connect(self.process_voltage_sweep_result) + self.module.model.serial_data_received.connect( + self.process_voltage_sweep_result + ) self.module.model.serial_data_received.connect(self.print_info) self.module.model.serial_data_received.connect(self.read_position_data) self.module.model.serial_data_received.connect(self.process_reflection_data) - self.module.model.serial_data_received.connect(self.process_position_sweep_result) + self.module.model.serial_data_received.connect( + self.process_position_sweep_result + ) self.module.model.serial_data_received.connect(self.process_signalpath_data) @pyqtSlot(str, object) def process_signals(self, key: str, value: object) -> None: + """Slot for setting the tune and match frequency. + + Args: + key (str): The key of the signal. If the key is "set_tune_and_match", the tune and match method is called. + value (object): The value of the signal. The value is the frequency to tune and match to. + """ logger.debug("Received signal: %s", key) if key == "set_tune_and_match": self.tune_and_match(value) def tune_and_match(self, frequency: float) -> None: - """This method is called when this module already has a LUT table. It should then tune and match the probe coil to the specified frequency. + """This method is called when this module already has a LUT table. + + It should then tune and match the probe coil to the specified frequency. + + Args: + frequency (float): The frequency to tune and match to. """ if self.module.model.LUT is None: logger.error("Could not tune and match. No LUT available.") return elif self.module.model.LUT.TYPE == "Electrical": - tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) - confirmation = self.set_voltages(str(tuning_voltage), str(matching_voltage)) + tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages( + frequency + ) + _ = self.set_voltages(str(tuning_voltage), str(matching_voltage)) # We need to change the signal pathway to preamp to measure the reflection self.switch_to_atm() reflection = self.read_reflection(frequency) @@ -57,7 +87,9 @@ class AutoTMController(ModuleController): self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) elif self.module.model.LUT.TYPE == "Mechanical": - tuning_position, matching_position = self.module.model.LUT.get_positions(frequency) + tuning_position, matching_position = self.module.model.LUT.get_positions( + frequency + ) self.go_to_position(tuning_position, matching_position) self.switch_to_atm() # Switch to atm to measure the reflection @@ -68,7 +100,12 @@ class AutoTMController(ModuleController): # The Lime doesn"t like it if we send the command to switch to atm and then immediately send the command to measure the reflection. # So we wait a bit before starting the measurement - QTimer.singleShot(100, lambda: self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection)) + QTimer.singleShot( + 100, + lambda: self.module.nqrduck_signal.emit( + "confirm_tune_and_match", reflection + ), + ) def find_devices(self) -> None: """Scan for available serial devices and add them to the model as available devices.""" @@ -127,6 +164,7 @@ class AutoTMController(ModuleController): def start_frequency_sweep(self, start_frequency: str, stop_frequency: str) -> None: """This starts a frequency sweep on the device in the specified range. + The minimum start and stop frequency are specific to the AD4351 based frequency generator. Args: @@ -162,13 +200,8 @@ 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 = f"Could not start frequency sweep. Start and stop frequency must be between {MIN_FREQUENCY / 1e6} and {MAX_FREQUENCY / 1e6} MHz" + logger.error(error) self.module.view.add_info_text(error) return @@ -182,7 +215,7 @@ class AutoTMController(ModuleController): ) # Print the command 'fff' to the serial connection - command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step) + command = f"f{start_frequency}f{stop_frequency}f{frequency_step}" self.module.model.frequency_sweep_start = time.time() confirmation = self.send_command(command) if confirmation: @@ -191,20 +224,31 @@ class AutoTMController(ModuleController): self.module.view.create_frequency_sweep_spinner_dialog() @pyqtSlot(str) - def process_frequency_sweep_data(self, text : str) -> None: + def process_frequency_sweep_data(self, text: str) -> None: """This method is called when data is received from the serial connection during a frequency sweep. + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. """ - 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")) self.module.model.add_data_point(frequency, return_loss, phase) @pyqtSlot(str) - def process_measurement_data(self, text : str) -> None: + def process_measurement_data(self, text: str) -> None: """This method is called when data is received from the serial connection during a measurement. + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. """ if self.module.model.active_calibration is None and text.startswith("r"): logger.debug("Measurement finished") @@ -214,26 +258,35 @@ class AutoTMController(ModuleController): self.finish_frequency_sweep() @pyqtSlot(str) - def process_calibration_data(self, text : str) -> None: + def process_calibration_data(self, text: str) -> None: """This method is called when data is received from the serial connection during a calibration. + It processes the data and adds it to the model. - + Args: - calibration_type (str): The type of calibration that is being performed. + text (str): The data received from the serial connection. """ - if text.startswith("r") and self.module.model.active_calibration in ["short", "open", "load"]: - calibration_type = self.module.model.active_calibration + if text.startswith("r") and self.module.model.active_calibration in [ + "short", + "open", + "load", + ]: + calibration_type = self.module.model.active_calibration logger.debug(f"{calibration_type.capitalize()} calibration finished") - setattr(self.module.model, f"{calibration_type}_calibration", - S11Data(self.module.model.data_points.copy())) + setattr( + self.module.model, + f"{calibration_type}_calibration", + S11Data(self.module.model.data_points.copy()), + ) self.module.model.active_calibration = None self.module.view.frequency_sweep_spinner.hide() @pyqtSlot(str) - def process_voltage_sweep_result(self, text : str) -> None: + def process_voltage_sweep_result(self, text: str) -> None: """This method is called when data is received from the serial connection during a voltage sweep. + It processes the data and adds it to the model. - + Args: text (str): The data received from the serial connection. """ @@ -243,27 +296,42 @@ class AutoTMController(ModuleController): LUT = self.module.model.el_lut if LUT is not None: if LUT.is_incomplete(): - logger.debug("Received voltage sweep result: Tuning %s Matching %s", tuning_voltage, matching_voltage) - LUT.add_voltages(tuning_voltage, matching_voltage) - self.continue_or_finish_voltage_sweep(LUT) + logger.debug( + "Received voltage sweep result: Tuning %s Matching %s", + tuning_voltage, + matching_voltage, + ) + LUT.add_voltages(tuning_voltage, matching_voltage) + self.continue_or_finish_voltage_sweep(LUT) self.module.model.tuning_voltage = tuning_voltage self.module.model.matching_voltage = matching_voltage - logger.debug("Updated voltages: Tuning %s Matching %s", self.module.model.tuning_voltage, self.module.model.matching_voltage) + logger.debug( + "Updated voltages: Tuning %s Matching %s", + self.module.model.tuning_voltage, + self.module.model.matching_voltage, + ) - def finish_frequency_sweep(self): + def finish_frequency_sweep(self) -> None: """This method is called when a frequency sweep is finished. + It hides the frequency sweep spinner dialog and adds the data to the model. """ self.module.view.frequency_sweep_spinner.hide() self.module.model.frequency_sweep_stop = time.time() - duration = self.module.model.frequency_sweep_stop - self.module.model.frequency_sweep_start - self.module.view.add_info_text(f"Frequency sweep finished in {duration:.2f} seconds") + duration = ( + self.module.model.frequency_sweep_stop + - self.module.model.frequency_sweep_start + ) + self.module.view.add_info_text( + f"Frequency sweep finished in {duration:.2f} seconds" + ) - def continue_or_finish_voltage_sweep(self, LUT): + def continue_or_finish_voltage_sweep(self, LUT) -> None: """This method is called when a voltage sweep is finished. + It checks if the voltage sweep is finished or if the next voltage sweep should be started. - + Args: LUT (LookupTable): The lookup table that is being generated. """ @@ -274,10 +342,11 @@ class AutoTMController(ModuleController): # Finish voltage sweep self.finish_voltage_sweep(LUT) - def start_next_voltage_sweep(self, LUT): + def start_next_voltage_sweep(self, LUT) -> None: """This method is called when a voltage sweep is finished. + It starts the next voltage sweep. - + Args: LUT (LookupTable): The lookup table that is being generated. """ @@ -286,32 +355,38 @@ class AutoTMController(ModuleController): if self.module.view._ui_form.prevVoltagecheckBox.isChecked(): # Command format is soo # We use the currently set voltages - command = "s%so%so%s" % (next_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage) + command = f"s{next_frequency}o{self.module.model.tuning_voltage}o{self.module.model.matching_voltage}" else: - command = "s%s" % (next_frequency) - + command = f"s{next_frequency}" + LUT.started_frequency = next_frequency logger.debug("Starting next voltage sweep: %s", command) self.send_command(command) - def finish_voltage_sweep(self, LUT): + def finish_voltage_sweep(self, LUT) -> None: """This method is called when a voltage sweep is finished. + It hides the voltage sweep spinner dialog and adds the data to the model. - + Args: - LUT (LookupTable): The lookup table that is being generated. + LUT (LookupTable): The lookup table that is being generated. """ logger.debug("Voltage sweep finished") self.module.view.el_LUT_spinner.hide() self.module.model.LUT = LUT self.module.model.voltage_sweep_stop = time.time() - duration = self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start - self.module.view.add_info_text(f"Voltage sweep finished in {duration:.2f} seconds") + duration = ( + self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start + ) + self.module.view.add_info_text( + f"Voltage sweep finished in {duration:.2f} seconds" + ) self.module.nqrduck_signal.emit("LUT_finished", LUT) @pyqtSlot(str) - def print_info(self, text : str) -> None: + def print_info(self, text: str) -> None: """This method is called when data is received from the serial connection. + It prints the data to the info text box. Args: @@ -323,10 +398,16 @@ class AutoTMController(ModuleController): elif text.startswith("e"): text = text[1:] self.module.view.add_error_text(text) - + @pyqtSlot(str) - def read_position_data(self, text : str) -> None: - """This method is called when data is received from the serial connection.""" + def read_position_data(self, text: str) -> None: + """This method is called when data is received from the serial connection. + + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. + """ if text.startswith("p"): # Format is pm text = text[1:].split("m") @@ -335,13 +416,17 @@ class AutoTMController(ModuleController): self.module.model.matching_stepper.position = matching_position self.module.model.tuning_stepper.homed = True self.module.model.matching_stepper.homed = True - logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) + logger.debug( + "Tuning position: %s, Matching position: %s", + tuning_position, + matching_position, + ) self.module.view.on_active_stepper_changed() def on_ready_read(self) -> None: """This method is called when data is received from the serial connection.""" serial = self.module.model.serial - + while serial.canReadLine(): text = serial.readLine().data().decode().rstrip("\r\n") logger.debug("Received data: %s", text) @@ -349,10 +434,11 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.emit(text) @pyqtSlot(str) - def process_reflection_data(self, text): + def process_reflection_data(self, text: str) -> None: """This method is called when data is received from the serial connection. + It processes the data and adds it to the model. - + Args: text (str): The data received from the serial connection. """ @@ -367,7 +453,12 @@ class AutoTMController(ModuleController): 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. + + Args: + start_frequency (float): The start frequency in MHz. + stop_frequency (float): The stop frequency in MHz. """ logger.debug("Starting short calibration") self.module.model.init_short_calibration() @@ -377,7 +468,12 @@ class AutoTMController(ModuleController): 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. + + Args: + start_frequency (float) : The start frequency in MHz. + stop_frequency (float) : The stop frequency in MHz. """ logger.debug("Starting open calibration") self.module.model.init_open_calibration() @@ -387,7 +483,12 @@ class AutoTMController(ModuleController): 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. + + Args: + start_frequency (float) : The start frequency in MHz. + stop_frequency (float) : The stop frequency in MHz. """ logger.debug("Starting load calibration") self.module.model.init_load_calibration() @@ -395,6 +496,7 @@ class AutoTMController(ModuleController): 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. @TODO: Improvements to the calibrations can be made the following ways: @@ -406,17 +508,17 @@ 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: + if self.module.model.short_calibration is None: logger.error( "Could not calculate calibration. No short calibration data points available." ) return - if self.module.model.open_calibration == None: + if self.module.model.open_calibration is None: logger.error( "Could not calculate calibration. No open calibration data points available." ) return - if self.module.model.load_calibration == None: + if self.module.model.load_calibration is None: logger.error( "Could not calculate calibration. No load calibration data points available." ) @@ -459,6 +561,7 @@ class AutoTMController(ModuleController): def export_calibration(self, filename: str) -> None: """This method is called when the export calibration button is pressed. + It exports the data of the short, open and load calibration to a file. Args: @@ -466,19 +569,19 @@ 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: + if self.module.model.short_calibration is None: logger.error( "Could not export calibration. No short calibration data points available." ) return - if self.module.model.open_calibration == None: + if self.module.model.open_calibration is None: logger.error( "Could not export calibration. No open calibration data points available." ) return - if self.module.model.load_calibration == None: + if self.module.model.load_calibration is None: logger.error( "Could not export calibration. No load calibration data points available." ) @@ -496,6 +599,7 @@ class AutoTMController(ModuleController): def import_calibration(self, filename: str) -> None: """This method is called when the import calibration button is pressed. + It imports the data of the short, open and load calibration from a file. Args: @@ -528,7 +632,7 @@ class AutoTMController(ModuleController): def load_measurement(self, filename: str) -> None: """Load measurement from file. - + Args: filename (str): Path to file. """ @@ -542,6 +646,7 @@ class AutoTMController(ModuleController): def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None: """This method is called when the set voltages button is pressed. + It writes the specified tuning and matching voltage to the serial connection. Args: @@ -583,16 +688,22 @@ class AutoTMController(ModuleController): matching_voltage, ) - if tuning_voltage == self.module.model.tuning_voltage and matching_voltage == self.module.model.matching_voltage: + if ( + tuning_voltage == self.module.model.tuning_voltage + and matching_voltage == self.module.model.matching_voltage + ): logger.debug("Voltages already set") return - - command = "v%sv%s" % (tuning_voltage, matching_voltage) - + + command = f"v{tuning_voltage}v{matching_voltage}" + start_time = time.time() confirmation = self.send_command(command) - while matching_voltage != self.module.model.matching_voltage and tuning_voltage != self.module.model.tuning_voltage: + while ( + matching_voltage != self.module.model.matching_voltage + and tuning_voltage != self.module.model.tuning_voltage + ): QApplication.processEvents() # Check for timeout if time.time() - start_time > timeout_duration: @@ -604,7 +715,7 @@ class AutoTMController(ModuleController): else: logger.error("Could not set voltages") return confirmation - + ### Electrical Lookup Table ### def generate_electrical_lut( @@ -614,6 +725,7 @@ class AutoTMController(ModuleController): frequency_step: str, ) -> None: """This method is called when the generate LUT button is pressed. + It generates a lookup table for the specified frequency range and voltage resolution. Args: @@ -635,11 +747,7 @@ class AutoTMController(ModuleController): self.module.view.add_info_text(error) return - if ( - start_frequency < 0 - or stop_frequency < 0 - or frequency_step < 0 - ): + if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0: error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be positive" logger.error(error) self.module.view.add_info_text(error) @@ -669,9 +777,7 @@ class AutoTMController(ModuleController): # self.set_voltages("0", "0") # We create the lookup table - LUT = ElectricalLookupTable( - start_frequency, stop_frequency, frequency_step - ) + LUT = ElectricalLookupTable(start_frequency, stop_frequency, frequency_step) LUT.started_frequency = start_frequency @@ -679,27 +785,32 @@ class AutoTMController(ModuleController): if self.module.view._ui_form.prevVoltagecheckBox.isChecked(): # Command format is soo # We use the currently set voltages - logger.debug("Starting preset Voltage sweep with voltage Tuning: %s V and Matching: %s V", self.module.model.tuning_voltage, self.module.model.matching_voltage) - command = "s%so%so%s" % (start_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage) + logger.debug( + "Starting preset Voltage sweep with voltage Tuning: %s V and Matching: %s V", + self.module.model.tuning_voltage, + self.module.model.matching_voltage, + ) + command = f"s{start_frequency}o{self.module.model.tuning_voltage}o{self.module.model.matching_voltage}" else: - command = "s%s" % (start_frequency) - + command = f"s{start_frequency}" + # For timing of the voltage sweep self.module.model.voltage_sweep_start = time.time() confirmation = self.send_command(command) - # If the command was send successfully, we set the LUT + # If the command was send successfully, we set the LUT if confirmation: self.module.model.el_lut = LUT self.module.view.create_el_LUT_spinner_dialog() 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 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. """ if self.module.model.signal_path == "preamp": logger.debug("Already in preamp") return - + TIMEOUT = 1 # s logger.debug("Switching to preamp") self.send_command("cp") @@ -714,12 +825,13 @@ class AutoTMController(ModuleController): 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. + In this state the atm system can be used to measure the reflection coefficient of the probecoils. """ if self.module.model.signal_path == "atm": logger.debug("Already in atm mode") return - + TIMEOUT = 1 # s logger.debug("Switching to atm") self.send_command("ca") @@ -732,10 +844,11 @@ class AutoTMController(ModuleController): logger.error("Switching to atm timed out") break - def process_signalpath_data(self, text : str) -> None: + def process_signalpath_data(self, text: str) -> None: """This method is called when data is received from the serial connection. + It processes the data and adds it to the model. - + Args: text (str): The data received from the serial connection. """ @@ -765,7 +878,7 @@ class AutoTMController(ModuleController): ) return False - if self.module.model.serial.isOpen() == False: + if self.module.model.serial.isOpen() is 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" @@ -796,12 +909,13 @@ class AutoTMController(ModuleController): except Exception as e: logger.error("Could not send command. %s", e) - self.module.view.add_error_text("Could not send command. %s" % e) + self.module.view.add_error_text(f"Could not send command. {e}") ### Stepper Motor Control ### def homing(self) -> None: """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") @@ -812,6 +926,7 @@ class AutoTMController(ModuleController): @pyqtSlot(str) def on_stepper_changed(self, stepper: str) -> None: """This method is called when the stepper position is changed. + It sends the command to the atm system to change the stepper position. Args: @@ -824,25 +939,52 @@ class AutoTMController(ModuleController): elif stepper == "matching": self.module.model.active_stepper = self.module.model.matching_stepper - def validate_position(self, future_position: int, stepper : Stepper) -> bool: - """Validate the stepper's future position.""" + def validate_position(self, future_position: int, stepper: Stepper) -> bool: + """Validate the stepper's future position. + + Args: + future_position (int): The future position of the stepper. + stepper (Stepper): The stepper that is being moved. + + Returns: + bool: True if the position is valid, False otherwise. + """ if future_position < 0: - self.module.view.add_error_text("Could not move stepper. Stepper position cannot be negative") + self.module.view.add_error_text( + "Could not move stepper. Stepper position cannot be negative" + ) return False if future_position > stepper.MAX_STEPS: - self.module.view.add_error_text(f"Could not move stepper. Stepper position cannot be larger than {stepper.MAX_STEPS}") + self.module.view.add_error_text( + f"Could not move stepper. Stepper position cannot be larger than {stepper.MAX_STEPS}" + ) return False return True - def calculate_steps_for_absolute_move(self, target_position: int, stepper : Stepper) -> int: - """Calculate the number of steps for an absolute move.""" + def calculate_steps_for_absolute_move( + self, target_position: int, stepper: Stepper + ) -> int: + """Calculate the number of steps for an absolute move. + + Args: + target_position (int): The target position of the stepper. + stepper (Stepper): The stepper that is being moved. + + Returns: + int: The number of steps to move. + """ current_position = stepper.position return target_position - current_position - def send_stepper_command(self, steps: int, stepper : Stepper) -> None: - """Send a command to the stepper motor based on the number of steps.""" + def send_stepper_command(self, steps: int, stepper: Stepper) -> None: + """Send a command to the stepper motor based on the number of steps. + + Args: + steps (int): The number of steps to move. + stepper (Stepper): The stepper that is being moved. + """ # Here we handle backlash of the tuner # Determine the direction of the current steps backlash = 0 @@ -862,7 +1004,12 @@ class AutoTMController(ModuleController): return confirmation def on_relative_move(self, steps: str, stepper: Stepper = None) -> None: - """This method is called when the relative move button is pressed.""" + """This method is called when the relative move button is pressed. + + Args: + steps (str): The number of steps to move. + stepper (Stepper): The stepper that is being moved. Default is None. + """ timeout_duration = 15 # timeout in seconds start_time = time.time() @@ -874,9 +1021,11 @@ class AutoTMController(ModuleController): if future_position == stepper_position: logger.debug("Stepper already at position") return - + if self.validate_position(future_position, stepper): - confirmation = self.send_stepper_command(int(steps), stepper) # Convert the steps string to an integer + confirmation = self.send_stepper_command( + int(steps), stepper + ) # Convert the steps string to an integer while stepper_position == stepper.position: QApplication.processEvents() @@ -888,7 +1037,12 @@ class AutoTMController(ModuleController): return confirmation def on_absolute_move(self, steps: str, stepper: Stepper = None) -> None: - """This method is called when the absolute move button is pressed.""" + """This method is called when the absolute move button is pressed. + + Args: + steps (str): The number of steps to move. + stepper (Stepper): The stepper that is being moved. Default is None. + """ timeout_duration = 15 # timeout in seconds start_time = time.time() @@ -903,7 +1057,9 @@ class AutoTMController(ModuleController): return if self.validate_position(future_position, stepper): - actual_steps = self.calculate_steps_for_absolute_move(future_position, stepper) + actual_steps = self.calculate_steps_for_absolute_move( + future_position, stepper + ) confirmation = self.send_stepper_command(actual_steps, stepper) while stepper_position == stepper.position: @@ -917,9 +1073,9 @@ class AutoTMController(ModuleController): ### Position Saving and Loading ### - def load_positions(self, path : str) -> None: + def load_positions(self, path: str) -> None: """Load the saved positions from a json file. - + Args: path (str): The path to the json file. """ @@ -930,12 +1086,15 @@ class AutoTMController(ModuleController): positions = json.load(f) for position in positions: logger.debug("Loading position: %s", position) - self.add_position(position["frequency"], position["tuning_position"], position["matching_position"]) - + self.add_position( + position["frequency"], + position["tuning_position"], + position["matching_position"], + ) def save_positions(self, path: str) -> None: """Save the current positions to a json file. - + Args: path (str): The path to the json file. """ @@ -944,43 +1103,52 @@ class AutoTMController(ModuleController): json_position = [position.to_json() for position in positions] json.dump(json_position, f) - def add_position(self, frequency: str, tuning_position: str, matching_position: str) -> None: + def add_position( + self, frequency: str, tuning_position: str, matching_position: str + ) -> None: """Add a position to the lookup table. - + Args: frequency (str): The frequency of the position. tuning_position (str): The tuning position. matching_position (str): The matching position. """ logger.debug("Adding new position at %s MHz", frequency) - self.module.model.add_saved_position(frequency, tuning_position, matching_position) + self.module.model.add_saved_position( + frequency, tuning_position, matching_position + ) def on_go_to_position(self, position: SavedPosition) -> None: """Go to the specified position. - + Args: position (SavedPosition): The position to go to. """ logger.debug("Going to position: %s", position) - confirmation = self.on_absolute_move(position.tuning_position, self.module.model.tuning_stepper) + confirmation = self.on_absolute_move( + position.tuning_position, self.module.model.tuning_stepper + ) if confirmation: - self.on_absolute_move(position.matching_position, self.module.model.matching_stepper) + self.on_absolute_move( + position.matching_position, self.module.model.matching_stepper + ) def on_delete_position(self, position: SavedPosition) -> None: """Delete the specified position. - + Args: position (SavedPosition): The position to delete. """ logger.debug("Deleting position: %s", position) self.module.model.delete_saved_position(position) - #### Mechanical tuning and matching #### - def generate_mechanical_lut(self, start_frequency: str, stop_frequency: str, frequency_step: str) -> None: + def generate_mechanical_lut( + self, start_frequency: str, stop_frequency: str, frequency_step: str + ) -> None: """Generate a lookup table for the specified frequency range and voltage resolution. - + Args: start_frequency (str): The start frequency in Hz. stop_frequency (str): The stop frequency in Hz. @@ -999,11 +1167,7 @@ class AutoTMController(ModuleController): self.module.view.add_info_text(error) return - if ( - start_frequency < 0 - or stop_frequency < 0 - or frequency_step < 0 - ): + if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0: error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be positive" logger.error(error) self.module.view.add_info_text(error) @@ -1032,9 +1196,7 @@ class AutoTMController(ModuleController): self.switch_to_atm() # We create the lookup table - LUT = MechanicalLookupTable( - start_frequency, stop_frequency, frequency_step - ) + LUT = MechanicalLookupTable(start_frequency, stop_frequency, frequency_step) # Lock GUI self.module.view.create_mech_LUT_spinner_dialog() @@ -1043,9 +1205,12 @@ class AutoTMController(ModuleController): self.start_next_mechTM(LUT) - - def start_next_mechTM(self, LUT): - """Start the next mechanical tuning and matching sweep.""" + def start_next_mechTM(self, LUT) -> None: + """Start the next mechanical tuning and matching sweep. + + Args: + LUT (MechanicalLookupTable): The lookup table. + """ next_frequency = LUT.get_next_frequency() LUT.started_frequency = next_frequency logger.debug("Starting next mechanical tuning and matching:") @@ -1068,10 +1233,15 @@ class AutoTMController(ModuleController): matching_last_direction = self.module.model.matching_stepper.last_direction command = f"p{next_frequency}t{TUNING_RANGE},{TUNER_STEP_SIZE},{tuning_backlash},{tuning_last_direction}m{MATCHING_RANGE},{MATCHER_STEP_SIZE},{matching_backlash},{matching_last_direction}" - confirmation = self.send_command(command) + _ = self.send_command(command) @pyqtSlot(str) - def process_position_sweep_result(self, text): + def process_position_sweep_result(self, text) -> None: + """Process the result of the position sweep. + + Args: + text (str): The text received from the serial connection. + """ if text.startswith("z"): text = text[1:] # Format is z,m, @@ -1088,44 +1258,71 @@ class AutoTMController(ModuleController): self.module.model.matching_stepper.position = matching_position self.module.view.on_active_stepper_changed() - logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) + logger.debug( + "Tuning position: %s, Matching position: %s", + tuning_position, + matching_position, + ) LUT = self.module.model.mech_lut - logger.debug("Received position sweep result: %s %s", matching_position, tuning_position) + logger.debug( + "Received position sweep result: %s %s", + matching_position, + tuning_position, + ) LUT.add_positions(tuning_position, matching_position) self.continue_or_finish_position_sweep(LUT) - def continue_or_finish_position_sweep(self, LUT): - """Continue or finish the position sweep.""" + def continue_or_finish_position_sweep(self, LUT) -> None: + """Continue or finish the position sweep. + + Args: + LUT (MechanicalLookupTable): The lookup table. + """ if LUT.is_incomplete(): self.start_next_mechTM(LUT) else: self.finish_position_sweep(LUT) - def finish_position_sweep(self, LUT): - """Finish the position sweep.""" + def finish_position_sweep(self, LUT) -> None: + """Finish the position sweep. + + Args: + LUT (MechanicalLookupTable): The lookup table. + """ logger.debug("Finished position sweep") self.module.model.mech_lut = LUT self.module.model.LUT = LUT self.module.view.mech_LUT_spinner.hide() self.module.nqrduck_signal.emit("LUT_finished", LUT) - def go_to_position(self, tuning_position : int, matching_position : int) -> None: + def go_to_position(self, tuning_position: int, matching_position: int) -> None: """Go to the specified position. - + Args: - position (SavedPosition): The position to go to. + tuning_position (int): The tuning position. + matching_position (int): The matching position. """ - confirmation = self.on_absolute_move(tuning_position, self.module.model.tuning_stepper) + confirmation = self.on_absolute_move( + tuning_position, self.module.model.tuning_stepper + ) if confirmation: - confirmation = self.on_absolute_move(matching_position, self.module.model.matching_stepper) + confirmation = self.on_absolute_move( + matching_position, self.module.model.matching_stepper + ) if confirmation: return True - # This method isn't used anymore but it might be useful in the future so I'll keep it here - def read_reflection(self, frequency) -> float: - """Starts a reflection measurement and reads the reflection at the specified frequency.""" + def read_reflection(self, frequency : float) -> float: + """Starts a reflection measurement and reads the reflection at the specified frequency. + + Args: + frequency (float): The frequency at which to read the reflection. + + Returns: + float: The reflection at the specified frequency. + """ # We send the command to the atm system command = f"r{frequency}" try: @@ -1143,8 +1340,13 @@ class AutoTMController(ModuleController): while reflection is None: # Check if the timeout has been reached if time.time() - start_time > timeout_duration: - logger.error("Reading reflection timed out after %d seconds", timeout_duration) - self.module.view.add_error_text(f"Could not read reflection. Timed out after {timeout_duration} seconds") + logger.error( + "Reading reflection timed out after %d seconds", + timeout_duration, + ) + self.module.view.add_error_text( + f"Could not read reflection. Timed out after {timeout_duration} seconds" + ) return None # Refresh the reflection data @@ -1163,12 +1365,12 @@ class AutoTMController(ModuleController): else: logger.error("Could not read reflection. No confirmation received") - self.module.view.add_error_text("Could not read reflection. No confirmation received") + self.module.view.add_error_text( + "Could not read reflection. No confirmation received" + ) return None except Exception as e: logger.error("Could not read reflection. %s", e) self.module.view.add_error_text(f"Could not read reflection. {e}") return None - - diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index bb69231..be6c4a8 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -1,3 +1,10 @@ +"""The module model for the NQRduck AutoTM module. It is used to store the data and state of the AutoTM module. + +Additionally it includes the LookupTable class which is used to store tuning and matching voltages for different frequencies. + +The S11Data class is used to store the S11 data that is read in via the serial connection. +""" + import cmath import numpy as np import logging @@ -10,6 +17,7 @@ logger = logging.getLogger(__name__) class S11Data: + """This class is used to store the S11 data that is read in via the serial connection.""" FILE_EXTENSION = "s11" # Conversion factors - the data is generally sent and received in mV # These values are used to convert the data to dB and degrees @@ -19,26 +27,32 @@ class S11Data: PHASE_SLOPE = 10 # deg/mV def __init__(self, data_points: list) -> None: + """Initialize the S11 data.""" self.frequency = np.array([data_point[0] for data_point in data_points]) self.return_loss_mv = np.array([data_point[1] for data_point in data_points]) self.phase_mv = np.array([data_point[2] for data_point in data_points]) @property def millivolts(self): + """The reflection data in millivolts. This is the raw data that is read in via the serial connection.""" return self.frequency, self.return_loss_mv, self.phase_mv @property def return_loss_db(self): + """Returns the return loss in dB calculated from the return loss in mV.""" return ( self.return_loss_mv - self.CENTER_POINT_MAGNITUDE ) / self.MAGNITUDE_SLOPE @property - def phase_deg(self, phase_correction=True): - """Returns the absolute value of the phase in degrees + def phase_deg(self, phase_correction=True) -> np.array: + """Returns the absolute value of the phase in degrees. - Keyword Arguments: - phase_correction {bool} -- If True, the phase correction is applied. (default: {False}) + Args: + phase_correction (bool, optional): If True, the phase correction is applied. Defaults to True. + + Returns: + np.array: The absolute value of the phase in degrees. """ phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE if phase_correction: @@ -48,11 +62,12 @@ class S11Data: @property def phase_rad(self): + """Returns the phase in radians.""" return self.phase_deg * cmath.pi / 180 @property def gamma(self): - """Complex reflection coefficient""" + """Complex reflection coefficient.""" if len(self.return_loss_db) != len(self.phase_rad): raise ValueError("return_loss_db and phase_rad must be the same length") @@ -65,6 +80,7 @@ class S11Data: 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. @@ -145,6 +161,7 @@ class S11Data: return phase_data_corrected def to_json(self): + """Convert the S11 data to a JSON serializable format.""" return { "frequency": self.frequency.tolist(), "return_loss_mv": self.return_loss_mv.tolist(), @@ -153,6 +170,7 @@ class S11Data: @classmethod def from_json(cls, json): + """Create an S11Data object from a JSON serializable format.""" f = json["frequency"] rl = json["return_loss_mv"] p = json["phase_mv"] @@ -171,13 +189,14 @@ class LookupTable: stop_frequency: float, frequency_step: float, ) -> None: + """Initialize the lookup table.""" self.start_frequency = start_frequency self.stop_frequency = stop_frequency self.frequency_step = frequency_step # This is the frequency at which the tuning and matching process was started self.started_frequency = None - + def get_entry_number(self, frequency: float) -> int: """This method returns the entry number of the given frequency. @@ -190,57 +209,78 @@ class LookupTable: # Round to closest integer return int(round((frequency - self.start_frequency) / self.frequency_step)) -class Stepper: +class Stepper: + """This class is used to store the state of a stepper motor.""" def __init__(self) -> None: + """Initialize the stepper motor.""" self.homed = False self.position = 0 + class SavedPosition: """This class is used to store a saved position for tuning and matching of electrical probeheads.""" - def __init__(self, frequency: float, tuning_position : int, matching_position : int) -> None: + + def __init__( + self, frequency: float, tuning_position: int, matching_position: int + ) -> None: + """Initialize the saved position.""" self.frequency = frequency self.tuning_position = tuning_position self.matching_position = matching_position def to_json(self): + """Convert the saved position to a JSON serializable format.""" return { "frequency": self.frequency, "tuning_position": self.tuning_position, "matching_position": self.matching_position, } + class TuningStepper(Stepper): + """This class is used to store the state of the tuning stepper motor.""" TYPE = "Tuning" MAX_STEPS = 1e6 BACKLASH_STEPS = 60 def __init__(self) -> None: + """Initialize the tuning stepper motor.""" super().__init__() - # Backlash stepper + # Backlash stepper self.last_direction = None - + + class MatchingStepper(Stepper): + """This class is used to store the state of the matching stepper motor.""" TYPE = "Matching" MAX_STEPS = 1e6 BACKLASH_STEPS = 0 def __init__(self) -> None: + """Initialize the matching stepper motor.""" super().__init__() self.last_direction = None + class ElectricalLookupTable(LookupTable): + """This class is used to store a lookup table for tuning and matching of electrical probeheads.""" TYPE = "Electrical" - def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None: + def __init__( + self, start_frequency: float, stop_frequency: float, frequency_step: float + ) -> None: + """Initialize the lookup table.""" super().__init__(start_frequency, stop_frequency, frequency_step) self.init_voltages() def init_voltages(self) -> None: """Initialize the lookup table with default values.""" for frequency in np.arange( - self.start_frequency, self.stop_frequency + self.frequency_step, self.frequency_step + self.start_frequency, + self.stop_frequency + self.frequency_step, + self.frequency_step, ): self.started_frequency = frequency self.add_voltages(None, None) @@ -250,7 +290,7 @@ class ElectricalLookupTable(LookupTable): Args: tuning_voltage (float): The tuning voltage for the given frequency. - matching_voltage (float): The matching voltage for the given frequency. + matching_voltage (float): The matching voltage for the given frequency. """ self.data[self.started_frequency] = (tuning_voltage, matching_voltage) @@ -266,10 +306,11 @@ class ElectricalLookupTable(LookupTable): entry_number = self.get_entry_number(frequency) key = list(self.data.keys())[entry_number] return self.data[key] - + def is_incomplete(self) -> bool: - """This method returns True if the lookup table is incomplete, - i.e. if there are frequencies for which no the tuning or matching voltage is none. + """This method returns True if the lookup table is incomplete. + + I.e. if there are frequencies for which no the tuning or matching voltage is none. Returns: bool: True if the lookup table is incomplete, False otherwise. @@ -293,19 +334,25 @@ class ElectricalLookupTable(LookupTable): return None + class MechanicalLookupTable(LookupTable): + """This class is used to store a lookup table for tuning and matching of mechanical probeheads.""" # Hmm duplicate code TYPE = "Mechanical" - - def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None: + def __init__( + self, start_frequency: float, stop_frequency: float, frequency_step: float + ) -> None: + """Initialize the lookup table.""" super().__init__(start_frequency, stop_frequency, frequency_step) self.init_positions() def init_positions(self) -> None: """Initialize the lookup table with default values.""" for frequency in np.arange( - self.start_frequency, self.stop_frequency + self.frequency_step, self.frequency_step + self.start_frequency, + self.stop_frequency + self.frequency_step, + self.frequency_step, ): self.started_frequency = frequency self.add_positions(None, None) @@ -315,7 +362,7 @@ class MechanicalLookupTable(LookupTable): Args: tuning_position (int): The tuning position for the given frequency. - matching_position (int): The matching position for the given frequency. + matching_position (int): The matching position for the given frequency. """ self.data[self.started_frequency] = (tuning_position, matching_position) @@ -331,10 +378,11 @@ class MechanicalLookupTable(LookupTable): entry_number = self.get_entry_number(frequency) key = list(self.data.keys())[entry_number] return self.data[key] - + def is_incomplete(self) -> bool: - """This method returns True if the lookup table is incomplete, - i.e. if there are frequencies for which no the tuning or matching position is none. + """This method returns True if the lookup table is incomplete. + + I.e. if there are frequencies for which no the tuning or matching position is none. Returns: bool: True if the lookup table is incomplete, False otherwise. @@ -345,7 +393,7 @@ class MechanicalLookupTable(LookupTable): for tuning_position, matching_position in self.data.values() ] ) - + def get_next_frequency(self) -> float: """This method returns the next frequency for which the tuning and matching position is not yet set. @@ -357,8 +405,10 @@ class MechanicalLookupTable(LookupTable): return frequency return None -class AutoTMModel(ModuleModel): + +class AutoTMModel(ModuleModel): + """The module model for the NQRduck AutoTM module. It is used to store the data and state of the AutoTM module.""" available_devices_changed = pyqtSignal(list) serial_changed = pyqtSignal(QSerialPort) data_points_changed = pyqtSignal(list) @@ -372,6 +422,7 @@ class AutoTMModel(ModuleModel): measurement_finished = pyqtSignal(S11Data) def __init__(self, module) -> None: + """Initialize the AutoTM model.""" super().__init__(module) self.data_points = [] self.active_calibration = None @@ -398,6 +449,7 @@ class AutoTMModel(ModuleModel): @property def available_devices(self): + """The available_devices property is used to store the available serial devices.""" return self._available_devices @available_devices.setter @@ -419,6 +471,7 @@ class AutoTMModel(ModuleModel): self, frequency: float, return_loss: float, phase: float ) -> None: """Add a data point to the model. These data points are our intermediate data points read in via the serial connection. + They will be saved in the according properties later on. """ self.data_points.append((frequency, return_loss, phase)) @@ -431,16 +484,21 @@ class AutoTMModel(ModuleModel): @property def saved_positions(self): + """The saved_positions property is used to store the saved positions for tuning and matching of the probeheads.""" return self._saved_positions - + @saved_positions.setter def saved_positions(self, value): self._saved_positions = value self.saved_positions_changed.emit(value) - def add_saved_position(self, frequency: float, tuning_position: int, matching_position: int) -> None: + def add_saved_position( + self, frequency: float, tuning_position: int, matching_position: int + ) -> None: """Add a saved position to the model.""" - self.saved_positions.append(SavedPosition(frequency, tuning_position, matching_position)) + self.saved_positions.append( + SavedPosition(frequency, tuning_position, matching_position) + ) self.saved_positions_changed.emit(self.saved_positions) def delete_saved_position(self, position: SavedPosition) -> None: @@ -451,6 +509,7 @@ class AutoTMModel(ModuleModel): @property def measurement(self): """The measurement property is used to store the current measurement. + This is the measurement that is shown in the main S11 plot """ return self._measurement @@ -463,8 +522,9 @@ class AutoTMModel(ModuleModel): @property def active_stepper(self): + """The active_stepper property is used to store the active stepper motor.""" return self._active_stepper - + @active_stepper.setter def active_stepper(self, value): self._active_stepper = value @@ -474,6 +534,7 @@ class AutoTMModel(ModuleModel): @property def active_calibration(self): + """The active_calibration property is used to store the active calibration type.""" return self._active_calibration @active_calibration.setter @@ -482,6 +543,7 @@ class AutoTMModel(ModuleModel): @property def short_calibration(self): + """The short_calibration property is used to store the short calibration data.""" return self._short_calibration @short_calibration.setter @@ -497,6 +559,7 @@ class AutoTMModel(ModuleModel): @property def open_calibration(self): + """The open calibration data.""" return self._open_calibration @open_calibration.setter @@ -512,6 +575,7 @@ class AutoTMModel(ModuleModel): @property def load_calibration(self): + """The load calibration data.""" return self._load_calibration @load_calibration.setter @@ -527,6 +591,7 @@ class AutoTMModel(ModuleModel): @property def calibration(self): + """The calibration data.""" return self._calibration @calibration.setter @@ -536,6 +601,7 @@ class AutoTMModel(ModuleModel): @property def LUT(self): + """The lookup table for tuning and matching of the probeheads.""" return self._LUT @LUT.setter diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 6b8784a..09cdd75 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -1,3 +1,5 @@ +"""This module contains the view class for the AutoTM module.""" + import logging from datetime import datetime import cmath @@ -27,7 +29,9 @@ logger = logging.getLogger(__name__) class AutoTMView(ModuleView): + """The view class for the AutoTM module.""" def __init__(self, module): + """Initializes the AutoTM view.""" super().__init__(module) widget = QWidget() @@ -127,11 +131,27 @@ class AutoTMView(ModuleView): self._ui_form.startButton.setIconSize(self._ui_form.startButton.size()) # Stepper selection - self._ui_form.stepperselectBox.currentIndexChanged.connect(lambda: self.module.controller.on_stepper_changed(self._ui_form.stepperselectBox.currentText())) - self._ui_form.increaseButton.clicked.connect(lambda: self.module.controller.on_relative_move(self._ui_form.stepsizeBox.text())) - self._ui_form.decreaseButton.clicked.connect(lambda: self.module.controller.on_relative_move("-" + self._ui_form.stepsizeBox.text())) + self._ui_form.stepperselectBox.currentIndexChanged.connect( + lambda: self.module.controller.on_stepper_changed( + self._ui_form.stepperselectBox.currentText() + ) + ) + self._ui_form.increaseButton.clicked.connect( + lambda: self.module.controller.on_relative_move( + self._ui_form.stepsizeBox.text() + ) + ) + self._ui_form.decreaseButton.clicked.connect( + lambda: self.module.controller.on_relative_move( + "-" + self._ui_form.stepsizeBox.text() + ) + ) - self._ui_form.absoluteGoButton.clicked.connect(lambda: self.module.controller.on_absolute_move(self._ui_form.absoluteposBox.text())) + self._ui_form.absoluteGoButton.clicked.connect( + lambda: self.module.controller.on_absolute_move( + self._ui_form.absoluteposBox.text() + ) + ) # Active stepper changed self.module.model.active_stepper_changed.connect(self.on_active_stepper_changed) @@ -175,6 +195,7 @@ class AutoTMView(ModuleView): 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") @@ -183,7 +204,11 @@ class AutoTMView(ModuleView): @pyqtSlot(list) 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. + + Args: + available_devices (list): List of available devices. + """ logger.debug("Updating available devices list") self._ui_form.portBox.clear() self._ui_form.portBox.addItems(available_devices) @@ -201,6 +226,7 @@ class AutoTMView(ModuleView): @pyqtSlot() def on_connect_button_clicked(self) -> None: """This method is called when the connect button is clicked. + It calls the connect method of the controller with the currently selected device. """ logger.debug("Connect button clicked") @@ -217,7 +243,7 @@ class AutoTMView(ModuleView): logger.debug("Updating serial connection label") if serial.isOpen(): self._ui_form.connectionLabel.setText(serial.portName()) - self.add_info_text("Connected to device %s" % serial.portName()) + self.add_info_text(f"Connected to device {serial.portName()}") # Change the connectButton to a disconnectButton self._ui_form.connectButton.setText("Disconnect") else: @@ -231,7 +257,9 @@ class AutoTMView(ModuleView): def on_active_stepper_changed(self) -> None: """Update the stepper position label according to the current stepper position.""" logger.debug("Updating stepper position label") - self._ui_form.stepperposLabel.setText(str(self.module.model.active_stepper.position)) + self._ui_form.stepperposLabel.setText( + str(self.module.model.active_stepper.position) + ) logger.debug("Updated stepper position label") # Only allow position change when stepper is homed @@ -253,6 +281,7 @@ class AutoTMView(ModuleView): @pyqtSlot() def on_position_button_clicked(self) -> None: """This method is called when the position button is clicked. + It opens the position window. """ logger.debug("Position button clicked") @@ -263,7 +292,7 @@ class AutoTMView(ModuleView): """Update the S11 plot with the current data points. Args: - data_points (list): List of data points to plot. + data (S11Data): The current S11 data points. @TODO: implement proper calibration. See the controller class for more information. """ @@ -328,7 +357,7 @@ class AutoTMView(ModuleView): """ # Add a timestamp to the text timestamp = datetime.now().strftime("%H:%M:%S") - text = "[%s] %s" % (timestamp, text) + text = f"[{timestamp}] {text}" text_label = QLabel(text) text_label.setStyleSheet("font-size: 25px;") self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label) @@ -351,7 +380,7 @@ class AutoTMView(ModuleView): ) # Add a timestamp to the text timestamp = datetime.now().strftime("%H:%M:%S") - text = "[%s] %s" % (timestamp, text) + text = f"[{timestamp}] {text}" text_label = QLabel(text) text_label.setStyleSheet("font-size: 25px; color: red;") @@ -365,7 +394,9 @@ class AutoTMView(ModuleView): def create_frequency_sweep_spinner_dialog(self) -> None: """Creates a frequency sweep spinner dialog.""" - self.frequency_sweep_spinner = self.LoadingSpinner("Performing frequency sweep ...", self) + self.frequency_sweep_spinner = self.LoadingSpinner( + "Performing frequency sweep ...", self + ) self.frequency_sweep_spinner.show() def create_el_LUT_spinner_dialog(self) -> None: @@ -375,7 +406,9 @@ class AutoTMView(ModuleView): def create_mech_LUT_spinner_dialog(self) -> None: """Creates a mechanical LUT spinner dialog.""" - self.mech_LUT_spinner = self.LoadingSpinner("Generating mechanical LUT ...", self) + self.mech_LUT_spinner = self.LoadingSpinner( + "Generating mechanical LUT ...", self + ) self.mech_LUT_spinner.show() def view_el_lut(self) -> None: @@ -417,7 +450,9 @@ class AutoTMView(ModuleView): self.module.controller.load_measurement(file_name) class StepperSavedPositionsWindow(QDialog): + """This class implements a window that shows the saved positions of the stepper.""" def __init__(self, module, parent=None): + """Initializes the StepperSavedPositionsWindow.""" super().__init__(parent) self.setParent(parent) self.module = module @@ -432,7 +467,13 @@ class AutoTMView(ModuleView): self.table_widget = QTableWidget() self.table_widget.setColumnCount(5) self.table_widget.setHorizontalHeaderLabels( - ["Frequency (MHz)", "Tuning Position", "Matching Position", "Button", "Delete"] + [ + "Frequency (MHz)", + "Tuning Position", + "Matching Position", + "Button", + "Delete", + ] ) self.table_widget.setColumnWidth(0, 150) @@ -461,8 +502,9 @@ class AutoTMView(ModuleView): main_layout.addWidget(self.table_widget) # On saved positions changed - self.module.model.saved_positions_changed.connect(self.on_saved_positions_changed) - + self.module.model.saved_positions_changed.connect( + self.on_saved_positions_changed + ) self.setLayout(main_layout) @@ -482,13 +524,13 @@ class AutoTMView(ModuleView): def on_load_position_button_clicked(self) -> None: """File picker for loading a position from a file.""" filename = self.file_selector("load") - logger.debug("Loading position from %s" % filename) + logger.debug(f"Loading position from {filename}") self.module.controller.load_positions(filename) def on_save_position_button_clicked(self) -> None: """File picker for saving a position to a file.""" filename = self.file_selector("save") - logger.debug("Saving position to %s" % filename) + logger.debug(f"Saving position to {filename}") self.module.controller.save_positions(filename) def on_new_position_button_clicked(self) -> None: @@ -497,9 +539,9 @@ class AutoTMView(ModuleView): self.new_position_window = self.NewPositionWindow(self.module, self) self.new_position_window.show() - def on_saved_positions_changed(self) -> None: """This method is called when the saved positions changed. + It updates the table widget. """ logger.debug("Updating saved positions table") @@ -508,7 +550,9 @@ class AutoTMView(ModuleView): for row, position in enumerate(self.module.model.saved_positions): self.table_widget.insertRow(row) - self.table_widget.setItem(row, 0, QTableWidgetItem(str(position.frequency))) + self.table_widget.setItem( + row, 0, QTableWidgetItem(str(position.frequency)) + ) self.table_widget.setItem( row, 1, QTableWidgetItem(position.tuning_position) ) @@ -517,7 +561,8 @@ class AutoTMView(ModuleView): ) go_button = QPushButton("Go") go_button.clicked.connect( - lambda _, position=position: self.module.controller.on_go_to_position( + lambda _, + position=position: self.module.controller.on_go_to_position( position ) ) @@ -525,16 +570,19 @@ class AutoTMView(ModuleView): delete_button = QPushButton("Delete") delete_button.clicked.connect( - lambda _, position=position: self.module.controller.on_delete_position( + lambda _, + position=position: self.module.controller.on_delete_position( position ) ) self.table_widget.setCellWidget(row, 4, delete_button) - + logger.debug("Updated saved positions table") class NewPositionWindow(QDialog): + """This class implements a window for adding a new position.""" def __init__(self, module, parent=None): + """Initializes the NewPositionWindow.""" super().__init__(parent) self.setParent(parent) self.module = module @@ -578,22 +626,32 @@ class AutoTMView(ModuleView): data_layout = QVBoxLayout() # Apply button apply_button = QPushButton("Apply") - apply_button.clicked.connect(lambda: self.on_apply_button_clicked(frequency_edit.text(), tuning_edit.text(), matching_edit.text())) + apply_button.clicked.connect( + lambda: self.on_apply_button_clicked( + frequency_edit.text(), tuning_edit.text(), matching_edit.text() + ) + ) data_layout.addWidget(apply_button) main_layout.addLayout(data_layout) self.setLayout(main_layout) - def on_apply_button_clicked(self, frequency: str, tuning_position: str, matching_position: str) -> None: + def on_apply_button_clicked( + self, frequency: str, tuning_position: str, matching_position: str + ) -> None: """This method is called when the apply button is clicked.""" - self.module.controller.add_position(frequency, tuning_position, matching_position) + self.module.controller.add_position( + frequency, tuning_position, matching_position + ) # Close the calibration window self.close() + class LoadingSpinner(QDialog): """This class implements a spinner dialog that is shown during a frequency sweep.""" - def __init__(self, text : str, parent=None): + def __init__(self, text: str, parent=None): + """Initializes the LoadingSpinner.""" super().__init__(parent) self.setWindowTitle("Loading") self.setModal(True) @@ -611,7 +669,9 @@ class AutoTMView(ModuleView): self.spinner_movie.start() class LutWindow(QDialog): + """This class implements a window that shows the LUT.""" def __init__(self, module, parent=None): + """Initializes the LutWindow.""" super().__init__() self.module = module self.setParent(parent) @@ -641,7 +701,7 @@ class AutoTMView(ModuleView): self.table_widget.setHorizontalHeaderLabels( ["Frequency (MHz)", "Tuning Voltage", "Matching Voltage"] ) - + for row, frequency in enumerate(LUT.data.keys()): self.table_widget.insertRow(row) self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency))) @@ -659,7 +719,9 @@ class AutoTMView(ModuleView): tuning_voltage = str(LUT.data[frequency][0]) matching_voltage = str(LUT.data[frequency][1]) test_button.clicked.connect( - lambda _, tuning_voltage=tuning_voltage, matching_voltage=matching_voltage: self.module.controller.set_voltages( + lambda _, + tuning_voltage=tuning_voltage, + matching_voltage=matching_voltage: self.module.controller.set_voltages( tuning_voltage, matching_voltage ) ) @@ -668,11 +730,13 @@ class AutoTMView(ModuleView): tuning_position = str(LUT.data[frequency][0]) matching_position = str(LUT.data[frequency][1]) test_button.clicked.connect( - lambda _, tuning_position=tuning_position, matching_position=matching_position: self.module.controller.go_to_position( + lambda _, + tuning_position=tuning_position, + matching_position=matching_position: self.module.controller.go_to_position( tuning_position, matching_position ) ) - + self.table_widget.setCellWidget(row, 3, test_button) # Add table widget to main layout @@ -681,6 +745,7 @@ class AutoTMView(ModuleView): def test_lut(self): """This method is called when the Test LUT button is clicked. It sets all of the voltages from the lut with a small delay. + One can then view the matching on a seperate VNA. """ # This should be in the controller @@ -690,7 +755,9 @@ class AutoTMView(ModuleView): self.module.controller.set_voltages(tuning_voltage, matching_voltage) class CalibrationWindow(QDialog): + """The calibration Dialog.""" def __init__(self, module, parent=None): + """Initializes the CalibrationWindow.""" super().__init__(parent) self.setParent(parent) self.module = module @@ -791,18 +858,22 @@ class AutoTMView(ModuleView): ) def on_short_calibration_finished(self, short_calibration: "S11Data") -> None: + """This method is called when the short calibration has finished. It plots the calibration data on the short_plot widget.""" self.on_calibration_finished("short", self.short_plot, short_calibration) def on_open_calibration_finished(self, open_calibration: "S11Data") -> None: + """This method is called when the open calibration has finished. It plots the calibration data on the open_plot widget.""" self.on_calibration_finished("open", self.open_plot, open_calibration) def on_load_calibration_finished(self, load_calibration: "S11Data") -> None: + """This method is called when the load calibration has finished. It plots the calibration data on the load_plot widget.""" self.on_calibration_finished("load", self.load_plot, load_calibration) 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. """ frequency = data.frequency @@ -829,13 +900,14 @@ class AutoTMView(ModuleView): widget.canvas.flush_events() def on_export_button_clicked(self) -> None: + """Called when the export button was clicked.""" filedialog = QFileDialog() filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) filedialog.setNameFilter("calibration files (*.cal)") filedialog.setDefaultSuffix("cal") filedialog.exec() filename = filedialog.selectedFiles()[0] - logger.debug("Exporting calibration to %s" % filename) + logger.debug(f"Exporting calibration to {filename}") self.module.controller.export_calibration(filename) def on_import_button_clicked(self) -> None: @@ -846,7 +918,7 @@ class AutoTMView(ModuleView): filedialog.setDefaultSuffix("cal") filedialog.exec() filename = filedialog.selectedFiles()[0] - logger.debug("Importing calibration from %s" % filename) + logger.debug(f"Importing calibration from {filename}") self.module.controller.import_calibration(filename) def on_apply_button_clicked(self) -> None: