This commit is contained in:
jupfi 2024-05-08 16:26:57 +02:00
parent 3a23b126fe
commit bfbc20c2e9
5 changed files with 556 additions and 212 deletions

View file

@ -1 +1,3 @@
from .autotm import AutoTM as Module """The NQRduck AutoTM module. It is used to automatically tune and match magnetic resonance probe coils."""
from .autotm import AutoTM as Module

View file

@ -1,6 +1,8 @@
"""The Module creation snippet for the NQRduck AutoTM module."""
from nqrduck.module.module import Module from nqrduck.module.module import Module
from .model import AutoTMModel from .model import AutoTMModel
from .view import AutoTMView from .view import AutoTMView
from .controller import AutoTMController from .controller import AutoTMController
AutoTM = Module(AutoTMModel, AutoTMView, AutoTMController) AutoTM = Module(AutoTMModel, AutoTMView, AutoTMController)

File diff suppressed because it is too large Load diff

View file

@ -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 cmath
import numpy as np import numpy as np
import logging import logging
@ -10,6 +17,7 @@ logger = logging.getLogger(__name__)
class S11Data: class S11Data:
"""This class is used to store the S11 data that is read in via the serial connection."""
FILE_EXTENSION = "s11" FILE_EXTENSION = "s11"
# Conversion factors - the data is generally sent and received in mV # Conversion factors - the data is generally sent and received in mV
# These values are used to convert the data to dB and degrees # These values are used to convert the data to dB and degrees
@ -19,26 +27,32 @@ class S11Data:
PHASE_SLOPE = 10 # deg/mV PHASE_SLOPE = 10 # deg/mV
def __init__(self, data_points: list) -> None: 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.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.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]) self.phase_mv = np.array([data_point[2] for data_point in data_points])
@property @property
def millivolts(self): 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 return self.frequency, self.return_loss_mv, self.phase_mv
@property @property
def return_loss_db(self): def return_loss_db(self):
"""Returns the return loss in dB calculated from the return loss in mV."""
return ( return (
self.return_loss_mv - self.CENTER_POINT_MAGNITUDE self.return_loss_mv - self.CENTER_POINT_MAGNITUDE
) / self.MAGNITUDE_SLOPE ) / self.MAGNITUDE_SLOPE
@property @property
def phase_deg(self, phase_correction=True): def phase_deg(self, phase_correction=True) -> np.array:
"""Returns the absolute value of the phase in degrees """Returns the absolute value of the phase in degrees.
Keyword Arguments: Args:
phase_correction {bool} -- If True, the phase correction is applied. (default: {False}) 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 phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
if phase_correction: if phase_correction:
@ -48,11 +62,12 @@ class S11Data:
@property @property
def phase_rad(self): def phase_rad(self):
"""Returns the phase in radians."""
return self.phase_deg * cmath.pi / 180 return self.phase_deg * cmath.pi / 180
@property @property
def gamma(self): def gamma(self):
"""Complex reflection coefficient""" """Complex reflection coefficient."""
if len(self.return_loss_db) != len(self.phase_rad): if len(self.return_loss_db) != len(self.phase_rad):
raise ValueError("return_loss_db and phase_rad must be the same length") 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 self, frequency_data: np.array, phase_data: np.array
) -> np.array: ) -> np.array:
"""This method fixes the phase sign of the phase data. """This method fixes the phase sign of the phase data.
The AD8302 can only measure the absolute value of the phase. 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. 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. If the slope is negative, the phase is positive and vice versa.
@ -145,6 +161,7 @@ class S11Data:
return phase_data_corrected return phase_data_corrected
def to_json(self): def to_json(self):
"""Convert the S11 data to a JSON serializable format."""
return { return {
"frequency": self.frequency.tolist(), "frequency": self.frequency.tolist(),
"return_loss_mv": self.return_loss_mv.tolist(), "return_loss_mv": self.return_loss_mv.tolist(),
@ -153,6 +170,7 @@ class S11Data:
@classmethod @classmethod
def from_json(cls, json): def from_json(cls, json):
"""Create an S11Data object from a JSON serializable format."""
f = json["frequency"] f = json["frequency"]
rl = json["return_loss_mv"] rl = json["return_loss_mv"]
p = json["phase_mv"] p = json["phase_mv"]
@ -171,13 +189,14 @@ class LookupTable:
stop_frequency: float, stop_frequency: float,
frequency_step: float, frequency_step: float,
) -> None: ) -> None:
"""Initialize the lookup table."""
self.start_frequency = start_frequency self.start_frequency = start_frequency
self.stop_frequency = stop_frequency self.stop_frequency = stop_frequency
self.frequency_step = frequency_step self.frequency_step = frequency_step
# This is the frequency at which the tuning and matching process was started # This is the frequency at which the tuning and matching process was started
self.started_frequency = None self.started_frequency = None
def get_entry_number(self, frequency: float) -> int: def get_entry_number(self, frequency: float) -> int:
"""This method returns the entry number of the given frequency. """This method returns the entry number of the given frequency.
@ -190,57 +209,78 @@ class LookupTable:
# Round to closest integer # Round to closest integer
return int(round((frequency - self.start_frequency) / self.frequency_step)) 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: def __init__(self) -> None:
"""Initialize the stepper motor."""
self.homed = False self.homed = False
self.position = 0 self.position = 0
class SavedPosition: class SavedPosition:
"""This class is used to store a saved position for tuning and matching of electrical probeheads.""" """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.frequency = frequency
self.tuning_position = tuning_position self.tuning_position = tuning_position
self.matching_position = matching_position self.matching_position = matching_position
def to_json(self): def to_json(self):
"""Convert the saved position to a JSON serializable format."""
return { return {
"frequency": self.frequency, "frequency": self.frequency,
"tuning_position": self.tuning_position, "tuning_position": self.tuning_position,
"matching_position": self.matching_position, "matching_position": self.matching_position,
} }
class TuningStepper(Stepper): class TuningStepper(Stepper):
"""This class is used to store the state of the tuning stepper motor."""
TYPE = "Tuning" TYPE = "Tuning"
MAX_STEPS = 1e6 MAX_STEPS = 1e6
BACKLASH_STEPS = 60 BACKLASH_STEPS = 60
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the tuning stepper motor."""
super().__init__() super().__init__()
# Backlash stepper # Backlash stepper
self.last_direction = None self.last_direction = None
class MatchingStepper(Stepper): class MatchingStepper(Stepper):
"""This class is used to store the state of the matching stepper motor."""
TYPE = "Matching" TYPE = "Matching"
MAX_STEPS = 1e6 MAX_STEPS = 1e6
BACKLASH_STEPS = 0 BACKLASH_STEPS = 0
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the matching stepper motor."""
super().__init__() super().__init__()
self.last_direction = None self.last_direction = None
class ElectricalLookupTable(LookupTable): class ElectricalLookupTable(LookupTable):
"""This class is used to store a lookup table for tuning and matching of electrical probeheads."""
TYPE = "Electrical" 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) super().__init__(start_frequency, stop_frequency, frequency_step)
self.init_voltages() self.init_voltages()
def init_voltages(self) -> None: def init_voltages(self) -> None:
"""Initialize the lookup table with default values.""" """Initialize the lookup table with default values."""
for frequency in np.arange( 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.started_frequency = frequency
self.add_voltages(None, None) self.add_voltages(None, None)
@ -250,7 +290,7 @@ class ElectricalLookupTable(LookupTable):
Args: Args:
tuning_voltage (float): The tuning voltage for the given frequency. 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) self.data[self.started_frequency] = (tuning_voltage, matching_voltage)
@ -266,10 +306,11 @@ class ElectricalLookupTable(LookupTable):
entry_number = self.get_entry_number(frequency) entry_number = self.get_entry_number(frequency)
key = list(self.data.keys())[entry_number] key = list(self.data.keys())[entry_number]
return self.data[key] return self.data[key]
def is_incomplete(self) -> bool: def is_incomplete(self) -> bool:
"""This method returns True if the lookup table is incomplete, """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.
I.e. if there are frequencies for which no the tuning or matching voltage is none.
Returns: Returns:
bool: True if the lookup table is incomplete, False otherwise. bool: True if the lookup table is incomplete, False otherwise.
@ -293,19 +334,25 @@ class ElectricalLookupTable(LookupTable):
return None return None
class MechanicalLookupTable(LookupTable): class MechanicalLookupTable(LookupTable):
"""This class is used to store a lookup table for tuning and matching of mechanical probeheads."""
# Hmm duplicate code # Hmm duplicate code
TYPE = "Mechanical" 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) super().__init__(start_frequency, stop_frequency, frequency_step)
self.init_positions() self.init_positions()
def init_positions(self) -> None: def init_positions(self) -> None:
"""Initialize the lookup table with default values.""" """Initialize the lookup table with default values."""
for frequency in np.arange( 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.started_frequency = frequency
self.add_positions(None, None) self.add_positions(None, None)
@ -315,7 +362,7 @@ class MechanicalLookupTable(LookupTable):
Args: Args:
tuning_position (int): The tuning position for the given frequency. 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) self.data[self.started_frequency] = (tuning_position, matching_position)
@ -331,10 +378,11 @@ class MechanicalLookupTable(LookupTable):
entry_number = self.get_entry_number(frequency) entry_number = self.get_entry_number(frequency)
key = list(self.data.keys())[entry_number] key = list(self.data.keys())[entry_number]
return self.data[key] return self.data[key]
def is_incomplete(self) -> bool: def is_incomplete(self) -> bool:
"""This method returns True if the lookup table is incomplete, """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.
I.e. if there are frequencies for which no the tuning or matching position is none.
Returns: Returns:
bool: True if the lookup table is incomplete, False otherwise. 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() for tuning_position, matching_position in self.data.values()
] ]
) )
def get_next_frequency(self) -> float: def get_next_frequency(self) -> float:
"""This method returns the next frequency for which the tuning and matching position is not yet set. """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 frequency
return None 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) available_devices_changed = pyqtSignal(list)
serial_changed = pyqtSignal(QSerialPort) serial_changed = pyqtSignal(QSerialPort)
data_points_changed = pyqtSignal(list) data_points_changed = pyqtSignal(list)
@ -372,6 +422,7 @@ class AutoTMModel(ModuleModel):
measurement_finished = pyqtSignal(S11Data) measurement_finished = pyqtSignal(S11Data)
def __init__(self, module) -> None: def __init__(self, module) -> None:
"""Initialize the AutoTM model."""
super().__init__(module) super().__init__(module)
self.data_points = [] self.data_points = []
self.active_calibration = None self.active_calibration = None
@ -398,6 +449,7 @@ class AutoTMModel(ModuleModel):
@property @property
def available_devices(self): def available_devices(self):
"""The available_devices property is used to store the available serial devices."""
return self._available_devices return self._available_devices
@available_devices.setter @available_devices.setter
@ -419,6 +471,7 @@ class AutoTMModel(ModuleModel):
self, frequency: float, return_loss: float, phase: float self, frequency: float, return_loss: float, phase: float
) -> None: ) -> None:
"""Add a data point to the model. These data points are our intermediate data points read in via the serial connection. """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. They will be saved in the according properties later on.
""" """
self.data_points.append((frequency, return_loss, phase)) self.data_points.append((frequency, return_loss, phase))
@ -431,16 +484,21 @@ class AutoTMModel(ModuleModel):
@property @property
def saved_positions(self): 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 return self._saved_positions
@saved_positions.setter @saved_positions.setter
def saved_positions(self, value): def saved_positions(self, value):
self._saved_positions = value self._saved_positions = value
self.saved_positions_changed.emit(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.""" """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) self.saved_positions_changed.emit(self.saved_positions)
def delete_saved_position(self, position: SavedPosition) -> None: def delete_saved_position(self, position: SavedPosition) -> None:
@ -451,6 +509,7 @@ class AutoTMModel(ModuleModel):
@property @property
def measurement(self): def measurement(self):
"""The measurement property is used to store the current measurement. """The measurement property is used to store the current measurement.
This is the measurement that is shown in the main S11 plot This is the measurement that is shown in the main S11 plot
""" """
return self._measurement return self._measurement
@ -463,8 +522,9 @@ class AutoTMModel(ModuleModel):
@property @property
def active_stepper(self): def active_stepper(self):
"""The active_stepper property is used to store the active stepper motor."""
return self._active_stepper return self._active_stepper
@active_stepper.setter @active_stepper.setter
def active_stepper(self, value): def active_stepper(self, value):
self._active_stepper = value self._active_stepper = value
@ -474,6 +534,7 @@ class AutoTMModel(ModuleModel):
@property @property
def active_calibration(self): def active_calibration(self):
"""The active_calibration property is used to store the active calibration type."""
return self._active_calibration return self._active_calibration
@active_calibration.setter @active_calibration.setter
@ -482,6 +543,7 @@ class AutoTMModel(ModuleModel):
@property @property
def short_calibration(self): def short_calibration(self):
"""The short_calibration property is used to store the short calibration data."""
return self._short_calibration return self._short_calibration
@short_calibration.setter @short_calibration.setter
@ -497,6 +559,7 @@ class AutoTMModel(ModuleModel):
@property @property
def open_calibration(self): def open_calibration(self):
"""The open calibration data."""
return self._open_calibration return self._open_calibration
@open_calibration.setter @open_calibration.setter
@ -512,6 +575,7 @@ class AutoTMModel(ModuleModel):
@property @property
def load_calibration(self): def load_calibration(self):
"""The load calibration data."""
return self._load_calibration return self._load_calibration
@load_calibration.setter @load_calibration.setter
@ -527,6 +591,7 @@ class AutoTMModel(ModuleModel):
@property @property
def calibration(self): def calibration(self):
"""The calibration data."""
return self._calibration return self._calibration
@calibration.setter @calibration.setter
@ -536,6 +601,7 @@ class AutoTMModel(ModuleModel):
@property @property
def LUT(self): def LUT(self):
"""The lookup table for tuning and matching of the probeheads."""
return self._LUT return self._LUT
@LUT.setter @LUT.setter

View file

@ -1,3 +1,5 @@
"""This module contains the view class for the AutoTM module."""
import logging import logging
from datetime import datetime from datetime import datetime
import cmath import cmath
@ -27,7 +29,9 @@ logger = logging.getLogger(__name__)
class AutoTMView(ModuleView): class AutoTMView(ModuleView):
"""The view class for the AutoTM module."""
def __init__(self, module): def __init__(self, module):
"""Initializes the AutoTM view."""
super().__init__(module) super().__init__(module)
widget = QWidget() widget = QWidget()
@ -127,11 +131,27 @@ class AutoTMView(ModuleView):
self._ui_form.startButton.setIconSize(self._ui_form.startButton.size()) self._ui_form.startButton.setIconSize(self._ui_form.startButton.size())
# Stepper selection # Stepper selection
self._ui_form.stepperselectBox.currentIndexChanged.connect(lambda: self.module.controller.on_stepper_changed(self._ui_form.stepperselectBox.currentText())) self._ui_form.stepperselectBox.currentIndexChanged.connect(
self._ui_form.increaseButton.clicked.connect(lambda: self.module.controller.on_relative_move(self._ui_form.stepsizeBox.text())) lambda: self.module.controller.on_stepper_changed(
self._ui_form.decreaseButton.clicked.connect(lambda: self.module.controller.on_relative_move("-" + self._ui_form.stepsizeBox.text())) 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 # Active stepper changed
self.module.model.active_stepper_changed.connect(self.on_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: def on_calibration_button_clicked(self) -> None:
"""This method is called when the calibration button is clicked. """This method is called when the calibration button is clicked.
It opens the calibration window. It opens the calibration window.
""" """
logger.debug("Calibration button clicked") logger.debug("Calibration button clicked")
@ -183,7 +204,11 @@ class AutoTMView(ModuleView):
@pyqtSlot(list) @pyqtSlot(list)
def on_available_devices_changed(self, available_devices: list) -> None: def on_available_devices_changed(self, available_devices: list) -> None:
"""Update the available devices list in the view.""" """Update the available devices list in the view.
Args:
available_devices (list): List of available devices.
"""
logger.debug("Updating available devices list") logger.debug("Updating available devices list")
self._ui_form.portBox.clear() self._ui_form.portBox.clear()
self._ui_form.portBox.addItems(available_devices) self._ui_form.portBox.addItems(available_devices)
@ -201,6 +226,7 @@ class AutoTMView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_connect_button_clicked(self) -> None: def on_connect_button_clicked(self) -> None:
"""This method is called when the connect button is clicked. """This method is called when the connect button is clicked.
It calls the connect method of the controller with the currently selected device. It calls the connect method of the controller with the currently selected device.
""" """
logger.debug("Connect button clicked") logger.debug("Connect button clicked")
@ -217,7 +243,7 @@ class AutoTMView(ModuleView):
logger.debug("Updating serial connection label") logger.debug("Updating serial connection label")
if serial.isOpen(): if serial.isOpen():
self._ui_form.connectionLabel.setText(serial.portName()) 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 # Change the connectButton to a disconnectButton
self._ui_form.connectButton.setText("Disconnect") self._ui_form.connectButton.setText("Disconnect")
else: else:
@ -231,7 +257,9 @@ class AutoTMView(ModuleView):
def on_active_stepper_changed(self) -> None: def on_active_stepper_changed(self) -> None:
"""Update the stepper position label according to the current stepper position.""" """Update the stepper position label according to the current stepper position."""
logger.debug("Updating stepper position label") 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") logger.debug("Updated stepper position label")
# Only allow position change when stepper is homed # Only allow position change when stepper is homed
@ -253,6 +281,7 @@ class AutoTMView(ModuleView):
@pyqtSlot() @pyqtSlot()
def on_position_button_clicked(self) -> None: def on_position_button_clicked(self) -> None:
"""This method is called when the position button is clicked. """This method is called when the position button is clicked.
It opens the position window. It opens the position window.
""" """
logger.debug("Position button clicked") logger.debug("Position button clicked")
@ -263,7 +292,7 @@ class AutoTMView(ModuleView):
"""Update the S11 plot with the current data points. """Update the S11 plot with the current data points.
Args: Args:
data_points (list): List of data points to plot. data (S11Data): The current S11 data points.
@TODO: implement proper calibration. See the controller class for more information. @TODO: implement proper calibration. See the controller class for more information.
""" """
@ -328,7 +357,7 @@ class AutoTMView(ModuleView):
""" """
# Add a timestamp to the text # Add a timestamp to the text
timestamp = datetime.now().strftime("%H:%M:%S") timestamp = datetime.now().strftime("%H:%M:%S")
text = "[%s] %s" % (timestamp, text) text = f"[{timestamp}] {text}"
text_label = QLabel(text) text_label = QLabel(text)
text_label.setStyleSheet("font-size: 25px;") text_label.setStyleSheet("font-size: 25px;")
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label) self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
@ -351,7 +380,7 @@ class AutoTMView(ModuleView):
) )
# Add a timestamp to the text # Add a timestamp to the text
timestamp = datetime.now().strftime("%H:%M:%S") timestamp = datetime.now().strftime("%H:%M:%S")
text = "[%s] %s" % (timestamp, text) text = f"[{timestamp}] {text}"
text_label = QLabel(text) text_label = QLabel(text)
text_label.setStyleSheet("font-size: 25px; color: red;") text_label.setStyleSheet("font-size: 25px; color: red;")
@ -365,7 +394,9 @@ class AutoTMView(ModuleView):
def create_frequency_sweep_spinner_dialog(self) -> None: def create_frequency_sweep_spinner_dialog(self) -> None:
"""Creates a frequency sweep spinner dialog.""" """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() self.frequency_sweep_spinner.show()
def create_el_LUT_spinner_dialog(self) -> None: def create_el_LUT_spinner_dialog(self) -> None:
@ -375,7 +406,9 @@ class AutoTMView(ModuleView):
def create_mech_LUT_spinner_dialog(self) -> None: def create_mech_LUT_spinner_dialog(self) -> None:
"""Creates a mechanical LUT spinner dialog.""" """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() self.mech_LUT_spinner.show()
def view_el_lut(self) -> None: def view_el_lut(self) -> None:
@ -417,7 +450,9 @@ class AutoTMView(ModuleView):
self.module.controller.load_measurement(file_name) self.module.controller.load_measurement(file_name)
class StepperSavedPositionsWindow(QDialog): class StepperSavedPositionsWindow(QDialog):
"""This class implements a window that shows the saved positions of the stepper."""
def __init__(self, module, parent=None): def __init__(self, module, parent=None):
"""Initializes the StepperSavedPositionsWindow."""
super().__init__(parent) super().__init__(parent)
self.setParent(parent) self.setParent(parent)
self.module = module self.module = module
@ -432,7 +467,13 @@ class AutoTMView(ModuleView):
self.table_widget = QTableWidget() self.table_widget = QTableWidget()
self.table_widget.setColumnCount(5) self.table_widget.setColumnCount(5)
self.table_widget.setHorizontalHeaderLabels( 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) self.table_widget.setColumnWidth(0, 150)
@ -461,8 +502,9 @@ class AutoTMView(ModuleView):
main_layout.addWidget(self.table_widget) main_layout.addWidget(self.table_widget)
# On saved positions changed # 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) self.setLayout(main_layout)
@ -482,13 +524,13 @@ class AutoTMView(ModuleView):
def on_load_position_button_clicked(self) -> None: def on_load_position_button_clicked(self) -> None:
"""File picker for loading a position from a file.""" """File picker for loading a position from a file."""
filename = self.file_selector("load") 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) self.module.controller.load_positions(filename)
def on_save_position_button_clicked(self) -> None: def on_save_position_button_clicked(self) -> None:
"""File picker for saving a position to a file.""" """File picker for saving a position to a file."""
filename = self.file_selector("save") 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) self.module.controller.save_positions(filename)
def on_new_position_button_clicked(self) -> None: 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 = self.NewPositionWindow(self.module, self)
self.new_position_window.show() self.new_position_window.show()
def on_saved_positions_changed(self) -> None: def on_saved_positions_changed(self) -> None:
"""This method is called when the saved positions changed. """This method is called when the saved positions changed.
It updates the table widget. It updates the table widget.
""" """
logger.debug("Updating saved positions table") logger.debug("Updating saved positions table")
@ -508,7 +550,9 @@ class AutoTMView(ModuleView):
for row, position in enumerate(self.module.model.saved_positions): for row, position in enumerate(self.module.model.saved_positions):
self.table_widget.insertRow(row) 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( self.table_widget.setItem(
row, 1, QTableWidgetItem(position.tuning_position) row, 1, QTableWidgetItem(position.tuning_position)
) )
@ -517,7 +561,8 @@ class AutoTMView(ModuleView):
) )
go_button = QPushButton("Go") go_button = QPushButton("Go")
go_button.clicked.connect( 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 position
) )
) )
@ -525,16 +570,19 @@ class AutoTMView(ModuleView):
delete_button = QPushButton("Delete") delete_button = QPushButton("Delete")
delete_button.clicked.connect( delete_button.clicked.connect(
lambda _, position=position: self.module.controller.on_delete_position( lambda _,
position=position: self.module.controller.on_delete_position(
position position
) )
) )
self.table_widget.setCellWidget(row, 4, delete_button) self.table_widget.setCellWidget(row, 4, delete_button)
logger.debug("Updated saved positions table") logger.debug("Updated saved positions table")
class NewPositionWindow(QDialog): class NewPositionWindow(QDialog):
"""This class implements a window for adding a new position."""
def __init__(self, module, parent=None): def __init__(self, module, parent=None):
"""Initializes the NewPositionWindow."""
super().__init__(parent) super().__init__(parent)
self.setParent(parent) self.setParent(parent)
self.module = module self.module = module
@ -578,22 +626,32 @@ class AutoTMView(ModuleView):
data_layout = QVBoxLayout() data_layout = QVBoxLayout()
# Apply button # Apply button
apply_button = QPushButton("Apply") 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) data_layout.addWidget(apply_button)
main_layout.addLayout(data_layout) main_layout.addLayout(data_layout)
self.setLayout(main_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.""" """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 # Close the calibration window
self.close() self.close()
class LoadingSpinner(QDialog): class LoadingSpinner(QDialog):
"""This class implements a spinner dialog that is shown during a frequency sweep.""" """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) super().__init__(parent)
self.setWindowTitle("Loading") self.setWindowTitle("Loading")
self.setModal(True) self.setModal(True)
@ -611,7 +669,9 @@ class AutoTMView(ModuleView):
self.spinner_movie.start() self.spinner_movie.start()
class LutWindow(QDialog): class LutWindow(QDialog):
"""This class implements a window that shows the LUT."""
def __init__(self, module, parent=None): def __init__(self, module, parent=None):
"""Initializes the LutWindow."""
super().__init__() super().__init__()
self.module = module self.module = module
self.setParent(parent) self.setParent(parent)
@ -641,7 +701,7 @@ class AutoTMView(ModuleView):
self.table_widget.setHorizontalHeaderLabels( self.table_widget.setHorizontalHeaderLabels(
["Frequency (MHz)", "Tuning Voltage", "Matching Voltage"] ["Frequency (MHz)", "Tuning Voltage", "Matching Voltage"]
) )
for row, frequency in enumerate(LUT.data.keys()): for row, frequency in enumerate(LUT.data.keys()):
self.table_widget.insertRow(row) self.table_widget.insertRow(row)
self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency))) self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency)))
@ -659,7 +719,9 @@ class AutoTMView(ModuleView):
tuning_voltage = str(LUT.data[frequency][0]) tuning_voltage = str(LUT.data[frequency][0])
matching_voltage = str(LUT.data[frequency][1]) matching_voltage = str(LUT.data[frequency][1])
test_button.clicked.connect( 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 tuning_voltage, matching_voltage
) )
) )
@ -668,11 +730,13 @@ class AutoTMView(ModuleView):
tuning_position = str(LUT.data[frequency][0]) tuning_position = str(LUT.data[frequency][0])
matching_position = str(LUT.data[frequency][1]) matching_position = str(LUT.data[frequency][1])
test_button.clicked.connect( 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 tuning_position, matching_position
) )
) )
self.table_widget.setCellWidget(row, 3, test_button) self.table_widget.setCellWidget(row, 3, test_button)
# Add table widget to main layout # Add table widget to main layout
@ -681,6 +745,7 @@ class AutoTMView(ModuleView):
def test_lut(self): 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. """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. One can then view the matching on a seperate VNA.
""" """
# This should be in the controller # This should be in the controller
@ -690,7 +755,9 @@ class AutoTMView(ModuleView):
self.module.controller.set_voltages(tuning_voltage, matching_voltage) self.module.controller.set_voltages(tuning_voltage, matching_voltage)
class CalibrationWindow(QDialog): class CalibrationWindow(QDialog):
"""The calibration Dialog."""
def __init__(self, module, parent=None): def __init__(self, module, parent=None):
"""Initializes the CalibrationWindow."""
super().__init__(parent) super().__init__(parent)
self.setParent(parent) self.setParent(parent)
self.module = module self.module = module
@ -791,18 +858,22 @@ class AutoTMView(ModuleView):
) )
def on_short_calibration_finished(self, short_calibration: "S11Data") -> None: 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) self.on_calibration_finished("short", self.short_plot, short_calibration)
def on_open_calibration_finished(self, open_calibration: "S11Data") -> None: 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) self.on_calibration_finished("open", self.open_plot, open_calibration)
def on_load_calibration_finished(self, load_calibration: "S11Data") -> None: 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) self.on_calibration_finished("load", self.load_plot, load_calibration)
def on_calibration_finished( def on_calibration_finished(
self, type: str, widget: MplWidget, data: "S11Data" self, type: str, widget: MplWidget, data: "S11Data"
) -> None: ) -> None:
"""This method is called when a calibration has finished. """This method is called when a calibration has finished.
It plots the calibration data on the given widget. It plots the calibration data on the given widget.
""" """
frequency = data.frequency frequency = data.frequency
@ -829,13 +900,14 @@ class AutoTMView(ModuleView):
widget.canvas.flush_events() widget.canvas.flush_events()
def on_export_button_clicked(self) -> None: def on_export_button_clicked(self) -> None:
"""Called when the export button was clicked."""
filedialog = QFileDialog() filedialog = QFileDialog()
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
filedialog.setNameFilter("calibration files (*.cal)") filedialog.setNameFilter("calibration files (*.cal)")
filedialog.setDefaultSuffix("cal") filedialog.setDefaultSuffix("cal")
filedialog.exec() filedialog.exec()
filename = filedialog.selectedFiles()[0] filename = filedialog.selectedFiles()[0]
logger.debug("Exporting calibration to %s" % filename) logger.debug(f"Exporting calibration to {filename}")
self.module.controller.export_calibration(filename) self.module.controller.export_calibration(filename)
def on_import_button_clicked(self) -> None: def on_import_button_clicked(self) -> None:
@ -846,7 +918,7 @@ class AutoTMView(ModuleView):
filedialog.setDefaultSuffix("cal") filedialog.setDefaultSuffix("cal")
filedialog.exec() filedialog.exec()
filename = filedialog.selectedFiles()[0] filename = filedialog.selectedFiles()[0]
logger.debug("Importing calibration from %s" % filename) logger.debug(f"Importing calibration from {filename}")
self.module.controller.import_calibration(filename) self.module.controller.import_calibration(filename)
def on_apply_button_clicked(self) -> None: def on_apply_button_clicked(self) -> None: