mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-11-09 19:50:00 +00:00
Added exporting and importing of calibration.
This commit is contained in:
parent
711a04f945
commit
ae3a42f869
3 changed files with 105 additions and 32 deletions
|
@ -1,9 +1,11 @@
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import json
|
||||||
from serial.tools.list_ports import comports
|
from serial.tools.list_ports import comports
|
||||||
from PyQt6 import QtSerialPort
|
from PyQt6 import QtSerialPort
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
|
||||||
from nqrduck.module.module_controller import ModuleController
|
from nqrduck.module.module_controller import ModuleController
|
||||||
|
from .model import S11Data
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -63,24 +65,24 @@ class AutoTMController(ModuleController):
|
||||||
# If the text starts with 'r' and no calibration is active we know that the data is a measurement
|
# If the text starts with 'r' and no calibration is active we know that the data is a measurement
|
||||||
elif text.startswith("r") and self.module.model.active_calibration == None:
|
elif text.startswith("r") and self.module.model.active_calibration == None:
|
||||||
logger.debug("Measurement finished")
|
logger.debug("Measurement finished")
|
||||||
self.module.model.measurement = self.module.model.data_points.copy()
|
self.module.model.measurement = S11Data(self.module.model.data_points.copy())
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
# If the text starts with 'r' and a short calibration is active we know that the data is a short calibration
|
# If the text starts with 'r' and a short calibration is active we know that the data is a short calibration
|
||||||
elif text.startswith("r") and self.module.model.active_calibration == "short":
|
elif text.startswith("r") and self.module.model.active_calibration == "short":
|
||||||
logger.debug("Short calibration finished")
|
logger.debug("Short calibration finished")
|
||||||
self.module.model.short_calibration = self.module.model.data_points.copy()
|
self.module.model.short_calibration = S11Data(self.module.model.data_points.copy())
|
||||||
self.module.model.active_calibration = None
|
self.module.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
# If the text starts with 'r' and an open calibration is active we know that the data is an open calibration
|
# If the text starts with 'r' and an open calibration is active we know that the data is an open calibration
|
||||||
elif text.startswith("r") and self.module.model.active_calibration == "open":
|
elif text.startswith("r") and self.module.model.active_calibration == "open":
|
||||||
logger.debug("Open calibration finished")
|
logger.debug("Open calibration finished")
|
||||||
self.module.model.open_calibration = self.module.model.data_points.copy()
|
self.module.model.open_calibration = S11Data(self.module.model.data_points.copy())
|
||||||
self.module.model.active_calibration = None
|
self.module.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
# If the text starts with 'r' and a load calibration is active we know that the data is a load calibration
|
# If the text starts with 'r' and a load calibration is active we know that the data is a load calibration
|
||||||
elif text.startswith("r") and self.module.model.active_calibration == "load":
|
elif text.startswith("r") and self.module.model.active_calibration == "load":
|
||||||
logger.debug("Load calibration finished")
|
logger.debug("Load calibration finished")
|
||||||
self.module.model.load_calibration = self.module.model.data_points.copy()
|
self.module.model.load_calibration = S11Data(self.module.model.data_points.copy())
|
||||||
self.module.model.active_calibration = None
|
self.module.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
# If the text starts with 'i' we know that the data is an info message
|
# If the text starts with 'i' we know that the data is an info message
|
||||||
|
@ -123,20 +125,13 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Calculating calibration")
|
logger.debug("Calculating calibration")
|
||||||
# First we check if the short and open calibration data points are available
|
# 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 == None:
|
||||||
logger.error("No short calibration data points available")
|
logger.error("Could not calculate calibration. No short calibration data points available.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.module.model.open_calibration == None:
|
if self.module.model.open_calibration == None:
|
||||||
logger.error("No open calibration data points available")
|
logger.error("Could not calculate calibration. No open calibration data points available.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.module.model.load_calibration == None:
|
if self.module.model.load_calibration == None:
|
||||||
logger.error("No load calibration data points available")
|
logger.error("Could not calculate calibration. No load calibration data points available.")
|
||||||
return
|
|
||||||
|
|
||||||
# Then we check if the short, open and load calibration data points have the same length
|
|
||||||
if len(self.module.model.short_calibration) != len(self.module.model.open_calibration) or len(self.module.model.short_calibration) != len(self.module.model.load_calibration):
|
|
||||||
logger.error("The short, open and load calibration data points do not have the same length")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Then we calculate the calibration
|
# Then we calculate the calibration
|
||||||
|
@ -144,14 +139,14 @@ class AutoTMController(ModuleController):
|
||||||
ideal_gamma_open = 1
|
ideal_gamma_open = 1
|
||||||
ideal_gamma_load = 0
|
ideal_gamma_load = 0
|
||||||
|
|
||||||
short_calibration = [10 **(-returnloss_s[1]) for returnloss_s in self.module.model.short_calibration]
|
measured_gamma_short = self.module.model.short_calibration.gamma
|
||||||
open_calibration = [10 **(-returnloss_o[1]) for returnloss_o in self.module.model.open_calibration]
|
measured_gamma_open = self.module.model.open_calibration.gamma
|
||||||
load_calibration = [10 **(-returnloss_l[1]) for returnloss_l in self.module.model.load_calibration]
|
measured_gamma_load = self.module.model.load_calibration.gamma
|
||||||
|
|
||||||
e_00s = []
|
e_00s = []
|
||||||
e11s = []
|
e11s = []
|
||||||
delta_es = []
|
delta_es = []
|
||||||
for gamma_s, gamma_o, gamma_l in zip(short_calibration, open_calibration, load_calibration):
|
for gamma_s, gamma_o, gamma_l in zip(measured_gamma_short, measured_gamma_open, measured_gamma_load):
|
||||||
A = np.array([
|
A = np.array([
|
||||||
[1, ideal_gamma_short * gamma_s, -ideal_gamma_short],
|
[1, ideal_gamma_short * gamma_s, -ideal_gamma_short],
|
||||||
[1, ideal_gamma_open * gamma_o, -ideal_gamma_open],
|
[1, ideal_gamma_open * gamma_o, -ideal_gamma_open],
|
||||||
|
@ -167,4 +162,51 @@ class AutoTMController(ModuleController):
|
||||||
e11s.append(e11)
|
e11s.append(e11)
|
||||||
delta_es.append(delta_e)
|
delta_es.append(delta_e)
|
||||||
|
|
||||||
self.module.model.calibration = (e_00s, e11s, delta_es)
|
self.module.model.calibration = (e_00s, e11s, delta_es)
|
||||||
|
|
||||||
|
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:
|
||||||
|
filename (str): The filename of the file to export to.
|
||||||
|
"""
|
||||||
|
logger.debug("Exporting calibration")
|
||||||
|
# First we check if the short and open calibration data points are available
|
||||||
|
if self.module.model.short_calibration == None:
|
||||||
|
logger.error("Could not export calibration. No short calibration data points available.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.module.model.open_calibration == None:
|
||||||
|
logger.error("Could not export calibration. No open calibration data points available.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.module.model.load_calibration == None:
|
||||||
|
logger.error("Could not export calibration. No load calibration data points available.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Then we export the different calibrations as a json file
|
||||||
|
data = {
|
||||||
|
"short": self.module.model.short_calibration.to_json(),
|
||||||
|
"open": self.module.model.open_calibration.to_json(),
|
||||||
|
"load": self.module.model.load_calibration.to_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
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:
|
||||||
|
filename (str): The filename of the file to import from.
|
||||||
|
"""
|
||||||
|
logger.debug("Importing calibration")
|
||||||
|
|
||||||
|
# We import the different calibrations from a json file
|
||||||
|
with open(filename, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self.module.model.short_calibration = S11Data.from_json(data["short"])
|
||||||
|
self.module.model.open_calibration = S11Data.from_json(data["open"])
|
||||||
|
self.module.model.load_calibration = S11Data.from_json(data["load"])
|
|
@ -40,7 +40,22 @@ class S11Data:
|
||||||
@property
|
@property
|
||||||
def gamma(self):
|
def gamma(self):
|
||||||
"""Complex reflection coefficient"""
|
"""Complex reflection coefficient"""
|
||||||
return cmath.rect(10 ** (-self.return_loss_db / 20), self.phase_rad)
|
return map(cmath.rect, (10 ** (-self.return_loss_db / 20), self.phase_rad))
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"frequency": self.frequency.tolist(),
|
||||||
|
"return_loss_mv": self.return_loss_mv.tolist(),
|
||||||
|
"phase_mv": self.phase_mv.tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, json):
|
||||||
|
f = json["frequency"]
|
||||||
|
rl = json["return_loss_mv"]
|
||||||
|
p = json["phase_mv"]
|
||||||
|
data = [(f[i], rl[i], p[i]) for i in range(len(f))]
|
||||||
|
return cls(data)
|
||||||
|
|
||||||
class AutoTMModel(ModuleModel):
|
class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
|
@ -99,8 +114,8 @@ class AutoTMModel(ModuleModel):
|
||||||
@measurement.setter
|
@measurement.setter
|
||||||
def measurement(self, value):
|
def measurement(self, value):
|
||||||
"""The measurement value is a tuple of three lists: frequency, return loss and phase."""
|
"""The measurement value is a tuple of three lists: frequency, return loss and phase."""
|
||||||
self._measurement = S11Data(value)
|
self._measurement = value
|
||||||
self.measurement_finished.emit(self._measurement)
|
self.measurement_finished.emit(value)
|
||||||
|
|
||||||
# Calibration properties
|
# Calibration properties
|
||||||
|
|
||||||
|
@ -119,8 +134,8 @@ class AutoTMModel(ModuleModel):
|
||||||
@short_calibration.setter
|
@short_calibration.setter
|
||||||
def short_calibration(self, value):
|
def short_calibration(self, value):
|
||||||
logger.debug("Setting short calibration")
|
logger.debug("Setting short calibration")
|
||||||
self._short_calibration = S11Data(value)
|
self._short_calibration = value
|
||||||
self.short_calibration_finished.emit(self._short_calibration)
|
self.short_calibration_finished.emit(value)
|
||||||
|
|
||||||
def init_short_calibration(self):
|
def init_short_calibration(self):
|
||||||
"""This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration."""
|
"""This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration."""
|
||||||
|
@ -134,8 +149,8 @@ class AutoTMModel(ModuleModel):
|
||||||
@open_calibration.setter
|
@open_calibration.setter
|
||||||
def open_calibration(self, value):
|
def open_calibration(self, value):
|
||||||
logger.debug("Setting open calibration")
|
logger.debug("Setting open calibration")
|
||||||
self._open_calibration = S11Data(value)
|
self._open_calibration = value
|
||||||
self.open_calibration_finished.emit(self._open_calibration)
|
self.open_calibration_finished.emit(value)
|
||||||
|
|
||||||
def init_open_calibration(self):
|
def init_open_calibration(self):
|
||||||
"""This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration."""
|
"""This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration."""
|
||||||
|
@ -149,8 +164,8 @@ class AutoTMModel(ModuleModel):
|
||||||
@load_calibration.setter
|
@load_calibration.setter
|
||||||
def load_calibration(self, value):
|
def load_calibration(self, value):
|
||||||
logger.debug("Setting load calibration")
|
logger.debug("Setting load calibration")
|
||||||
self._load_calibration = S11Data(value)
|
self._load_calibration = value
|
||||||
self.load_calibration_finished.emit(self._load_calibration)
|
self.load_calibration_finished.emit(value)
|
||||||
|
|
||||||
def init_load_calibration(self):
|
def init_load_calibration(self):
|
||||||
"""This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration."""
|
"""This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration."""
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import smithplot
|
||||||
|
from smithplot import SmithAxes
|
||||||
from PyQt6.QtGui import QMovie
|
from PyQt6.QtGui import QMovie
|
||||||
from PyQt6.QtSerialPort import QSerialPort
|
from PyQt6.QtSerialPort import QSerialPort
|
||||||
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication, QHBoxLayout, QLineEdit, QPushButton, QDialog
|
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication, QHBoxLayout, QLineEdit, QPushButton, QDialog, QFileDialog
|
||||||
from PyQt6.QtCore import pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSlot, Qt
|
||||||
from nqrduck.module.module_view import ModuleView
|
from nqrduck.module.module_view import ModuleView
|
||||||
from nqrduck.contrib.mplwidget import MplWidget
|
from nqrduck.contrib.mplwidget import MplWidget
|
||||||
|
@ -129,6 +131,7 @@ class AutoTMView(ModuleView):
|
||||||
return_loss_db = data.return_loss_db
|
return_loss_db = data.return_loss_db
|
||||||
phase = data.phase_deg
|
phase = data.phase_deg
|
||||||
|
|
||||||
|
gamma = data.gamma
|
||||||
# Calibration test:
|
# Calibration test:
|
||||||
#calibration = self.module.model.calibration
|
#calibration = self.module.model.calibration
|
||||||
#e_00 = calibration[0]
|
#e_00 = calibration[0]
|
||||||
|
@ -335,12 +338,25 @@ 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:
|
||||||
"""This method is called when the export button is clicked. """
|
filedialog = QFileDialog()
|
||||||
pass
|
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)
|
||||||
|
self.module.controller.export_calibration(filename)
|
||||||
|
|
||||||
def on_import_button_clicked(self) -> None:
|
def on_import_button_clicked(self) -> None:
|
||||||
"""This method is called when the import button is clicked. """
|
"""This method is called when the import button is clicked. """
|
||||||
pass
|
filedialog = QFileDialog()
|
||||||
|
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
|
||||||
|
filedialog.setNameFilter("calibration files (*.cal)")
|
||||||
|
filedialog.setDefaultSuffix("cal")
|
||||||
|
filedialog.exec()
|
||||||
|
filename = filedialog.selectedFiles()[0]
|
||||||
|
logger.debug("Importing calibration from %s" % filename)
|
||||||
|
self.module.controller.import_calibration(filename)
|
||||||
|
|
||||||
def on_apply_button_clicked(self) -> None:
|
def on_apply_button_clicked(self) -> None:
|
||||||
"""This method is called when the apply button is clicked. """
|
"""This method is called when the apply button is clicked. """
|
||||||
|
|
Loading…
Reference in a new issue