From 6a36c9db5f4419d6ceb6c5f720d93eed20fb4e4c Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 10 Aug 2023 09:07:51 +0200 Subject: [PATCH] Added information on calibration. --- src/nqrduck_autotm/controller.py | 50 ++++++++++++++++++++++--------- src/nqrduck_autotm/model.py | 8 ++++- src/nqrduck_autotm/view.py | 51 ++++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 30 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 592f5d5..cf7cdf8 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -121,6 +121,14 @@ 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: Make calibration useful. Right now the calibration does not work for the probe coils. It completly messes up the S11 data. + For 50 Ohm reference loads the calibration makes the S11 data usable - one then gets a flat line at -50 dB. + The problem is probably two things: + 1. The ideal values for open, short and load should be measured with a VNA and then be loaded for the calibration. + The ideal values are probably not -1, 1 and 0 but will also show frequency dependent behaviour. + 2 The AD8302 chip only returns the absolute value of the phase. One would probably need to calculate the phase with various algorithms found in the literature. + Though Im not sure if these proposed algorithms would work for the AD8302 chip. """ logger.debug("Calculating calibration") # First we check if the short and open calibration data points are available @@ -143,26 +151,40 @@ class AutoTMController(ModuleController): measured_gamma_open = self.module.model.open_calibration.gamma measured_gamma_load = self.module.model.load_calibration.gamma - e_00s = [] - e11s = [] - delta_es = [] + E_Ds = [] + E_Ss = [] + E_ts = [] 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], - [1, ideal_gamma_load * gamma_l, -ideal_gamma_load] - ]) + # This is the solution from + # A = np.array([ + # [1, ideal_gamma_short * gamma_s, -ideal_gamma_short], + # [1, ideal_gamma_open * gamma_o, -ideal_gamma_open], + # [1, ideal_gamma_load * gamma_l, -ideal_gamma_load] + # ]) - B = np.array([gamma_s, gamma_o, gamma_l]) + # B = np.array([gamma_s, gamma_o, gamma_l]) # Solve the system - e_00, e11, delta_e = np.linalg.lstsq(A, B, rcond=None)[0] + # e_00, e11, delta_e = np.linalg.lstsq(A, B, rcond=None)[0] - e_00s.append(e_00) - e11s.append(e11) - delta_es.append(delta_e) + E_D = gamma_l + E_ = (2 * gamma_l - (gamma_s + gamma_o)) / (gamma_s - gamma_o) + E_S = (2 * (gamma_o + gamma_l) * (gamma_s + gamma_l)) / (gamma_s - gamma_o) - self.module.model.calibration = (e_00s, e11s, delta_es) + E_Ds.append(E_D) + E_Ss.append(E_S) + E_ts.append(E_) + # e_00 = gamma_l # Because here the reflection coefficient should be 0 + + # e11 = (gamma_o + gamma_o - 2 * e_00) / (gamma_o - gamma_s) + + # delta_e = -gamma_o + gamma_o* e11 + e_00 + + # e_00s.append(e_00) + # e11s.append(e11) + # delta_es.append(delta_e) + + self.module.model.calibration = (E_Ds, E_Ss, E_ts) def export_calibration(self, filename: str) -> None: """This method is called when the export calibration button is pressed. diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 355c079..fe3bab2 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -40,7 +40,12 @@ class S11Data: @property def gamma(self): """Complex reflection coefficient""" - return map(cmath.rect, (10 ** (-self.return_loss_db / 20), 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") + + return [cmath.rect(10 ** (-loss_db / 20), phase_rad) for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad)] + + def to_json(self): return { @@ -72,6 +77,7 @@ class AutoTMModel(ModuleModel): super().__init__(module) self.data_points = [] self.active_calibration = None + self.calibration = None @property def available_devices(self): diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index bb68374..8e5dd83 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -126,27 +126,48 @@ class AutoTMView(ModuleView): Args: data_points (list): List of data points to plot. + + @TODO: implement proper calibration. See the controller class for more information. """ frequency = data.frequency 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] - #e11 = calibration[1] - #delta_e = calibration[2] + # Plot complex reflection coefficient + """ import matplotlib.pyplot as plt + fig, ax = plt.subplots() + ax.plot([g.real for g in gamma], [g.imag for g in gamma]) + ax.set_aspect('equal') + ax.grid(True) + ax.set_title("Complex reflection coefficient") + ax.set_xlabel("Real") + ax.set_ylabel("Imaginary") + plt.show() + """ + + magnitude_ax = self._ui_form.S11Plot.canvas.ax + # @ TODO: implement proper calibration + if self.module.model.calibration is not None: + # Calibration test: + import cmath + calibration = self.module.model.calibration + E_D = calibration[0] + E_S = calibration[1] + E_t = calibration[2] - #y_corr = [(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(y)] - #import numpy as np - #y = [data_point[1] for data_point in self.module.model.data_points] - #open_calibration = [data_point[1] for data_point in self.module.model.open_calibration] - #load_calibration = [data_point[1] for data_point in self.module.model.load_calibration] - #short_calibration = [data_point[1] for data_point in self.module.model.short_calibration] - - #y_corr = np.array(y) - np.array(load_calibration) - #y_corr = y_corr - np.mean(y_corr) + # gamma_corr = [(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(gamma)] + gamma_corr = [(data_point - E_D[i]) / (E_S[i] * (data_point - E_D[i]) + E_t[i]) for i, data_point in enumerate(gamma)] + """ fig, ax = plt.subplots() + ax.plot([g.real for g in gamma_corr], [g.imag for g in gamma_corr]) + ax.set_aspect('equal') + ax.grid(True) + ax.set_title("Complex reflection coefficient") + ax.set_xlabel("Real") + ax.set_ylabel("Imaginary") + plt.show() """ + return_loss_db_corr = [-20 * cmath.log10(abs(g + 1e-12)) for g in gamma_corr] + magnitude_ax.plot(frequency, return_loss_db_corr, color="red") phase_ax = self._ui_form.S11Plot.canvas.ax.twinx() phase_ax.set_ylabel("Phase (deg)") @@ -154,7 +175,7 @@ class AutoTMView(ModuleView): phase_ax.set_ylim(-180, 180) phase_ax.invert_yaxis() - magnitude_ax = self._ui_form.S11Plot.canvas.ax + magnitude_ax.clear() magnitude_ax.set_xlabel("Frequency (MHz)") magnitude_ax.set_ylabel("S11 (dB)")