Added exporting and importing of calibration.

This commit is contained in:
jupfi 2023-08-09 16:32:13 +02:00
parent 711a04f945
commit ae3a42f869
3 changed files with 105 additions and 32 deletions

View file

@ -1,9 +1,11 @@
import logging
import numpy as np
import json
from serial.tools.list_ports import comports
from PyQt6 import QtSerialPort
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from nqrduck.module.module_controller import ModuleController
from .model import S11Data
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
elif text.startswith("r") and self.module.model.active_calibration == None:
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()
# 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":
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.view.frequency_sweep_spinner.hide()
# If the text starts with 'r' and an open calibration is active we know that the data is an open calibration
elif text.startswith("r") and self.module.model.active_calibration == "open":
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.view.frequency_sweep_spinner.hide()
# If the text starts with 'r' and a load calibration is active we know that the data is a load calibration
elif text.startswith("r") and self.module.model.active_calibration == "load":
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.view.frequency_sweep_spinner.hide()
# 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")
# First we check if the short and open calibration data points are available
if self.module.model.short_calibration == None:
logger.error("No short calibration data points available")
logger.error("Could not calculate calibration. No short calibration data points available.")
return
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
if self.module.model.load_calibration == None:
logger.error("No load calibration data points available")
return
# Then we check if the short, open and load calibration data points have the same length
if len(self.module.model.short_calibration) != len(self.module.model.open_calibration) or len(self.module.model.short_calibration) != len(self.module.model.load_calibration):
logger.error("The short, open and load calibration data points do not have the same length")
logger.error("Could not calculate calibration. No load calibration data points available.")
return
# Then we calculate the calibration
@ -144,14 +139,14 @@ class AutoTMController(ModuleController):
ideal_gamma_open = 1
ideal_gamma_load = 0
short_calibration = [10 **(-returnloss_s[1]) for returnloss_s in self.module.model.short_calibration]
open_calibration = [10 **(-returnloss_o[1]) for returnloss_o in self.module.model.open_calibration]
load_calibration = [10 **(-returnloss_l[1]) for returnloss_l in self.module.model.load_calibration]
measured_gamma_short = self.module.model.short_calibration.gamma
measured_gamma_open = self.module.model.open_calibration.gamma
measured_gamma_load = self.module.model.load_calibration.gamma
e_00s = []
e11s = []
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([
[1, ideal_gamma_short * gamma_s, -ideal_gamma_short],
[1, ideal_gamma_open * gamma_o, -ideal_gamma_open],
@ -167,4 +162,51 @@ class AutoTMController(ModuleController):
e11s.append(e11)
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"])

View file

@ -40,7 +40,22 @@ class S11Data:
@property
def gamma(self):
"""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):
@ -99,8 +114,8 @@ class AutoTMModel(ModuleModel):
@measurement.setter
def measurement(self, value):
"""The measurement value is a tuple of three lists: frequency, return loss and phase."""
self._measurement = S11Data(value)
self.measurement_finished.emit(self._measurement)
self._measurement = value
self.measurement_finished.emit(value)
# Calibration properties
@ -119,8 +134,8 @@ class AutoTMModel(ModuleModel):
@short_calibration.setter
def short_calibration(self, value):
logger.debug("Setting short calibration")
self._short_calibration = S11Data(value)
self.short_calibration_finished.emit(self._short_calibration)
self._short_calibration = value
self.short_calibration_finished.emit(value)
def init_short_calibration(self):
"""This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration."""
@ -134,8 +149,8 @@ class AutoTMModel(ModuleModel):
@open_calibration.setter
def open_calibration(self, value):
logger.debug("Setting open calibration")
self._open_calibration = S11Data(value)
self.open_calibration_finished.emit(self._open_calibration)
self._open_calibration = value
self.open_calibration_finished.emit(value)
def init_open_calibration(self):
"""This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration."""
@ -149,8 +164,8 @@ class AutoTMModel(ModuleModel):
@load_calibration.setter
def load_calibration(self, value):
logger.debug("Setting load calibration")
self._load_calibration = S11Data(value)
self.load_calibration_finished.emit(self._load_calibration)
self._load_calibration = value
self.load_calibration_finished.emit(value)
def init_load_calibration(self):
"""This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration."""

View file

@ -1,9 +1,11 @@
import logging
from datetime import datetime
from pathlib import Path
import smithplot
from smithplot import SmithAxes
from PyQt6.QtGui import QMovie
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 nqrduck.module.module_view import ModuleView
from nqrduck.contrib.mplwidget import MplWidget
@ -129,6 +131,7 @@ class AutoTMView(ModuleView):
return_loss_db = data.return_loss_db
phase = data.phase_deg
gamma = data.gamma
# Calibration test:
#calibration = self.module.model.calibration
#e_00 = calibration[0]
@ -335,12 +338,25 @@ class AutoTMView(ModuleView):
widget.canvas.flush_events()
def on_export_button_clicked(self) -> None:
"""This method is called when the export button is clicked. """
pass
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)
self.module.controller.export_calibration(filename)
def on_import_button_clicked(self) -> None:
"""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:
"""This method is called when the apply button is clicked. """