From 89ed805411fc4b5d1eca2b64f965e80ff538f3da Mon Sep 17 00:00:00 2001 From: Leon Teichroeb Date: Wed, 4 Aug 2021 17:48:58 +0200 Subject: [PATCH] Implemented coil constant calibration --- src/calibration.py | 48 ++++++++++++++++++++++++++++++++++++++++++- src/user_interface.py | 46 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/calibration.py b/src/calibration.py index d30af1c..b6aca63 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -1,3 +1,4 @@ +import math import time from datetime import datetime from threading import Thread @@ -103,6 +104,10 @@ class AmbientFieldCalibration(Thread): class CoilConstantCalibration(Thread): + MEASUREMENT_RANGE = 3 # A. Will extend into negative and positive sign + MEASUREMENT_POINTS = 6 # Excludes zero. eg 0.5, 1, 1.5, 2 + SETTLE_TIME = 0.5 # Time until new measurement is ready after setting current + def __init__(self, view_queue): Thread.__init__(self) self.view_queue = view_queue @@ -135,8 +140,49 @@ class CoilConstantCalibration(Thread): # All generated fields will be compared to this using a simple difference method ambient_field = g.MAGNETOMETER.field # The coil constant must be determined for every axis + currents = np.linspace(-self.MEASUREMENT_RANGE, self.MEASUREMENT_RANGE, self.MEASUREMENT_POINTS * 2 + 1) + currents = np.delete(currents, self.MEASUREMENT_POINTS) + coil_constants = np.zeros(3) + k_deviations = np.zeros(3) for i in range(3): - pass + k_samples = [] + for c_idx, c in enumerate(currents): + # Set new progress indicator for UI + self.put_message('progress', ((c_idx / (self.MEASUREMENT_POINTS * 2)) + i) / 3) + + # Set current + c_vec = [0, 0, 0] + c_vec[i] = c + self.cage_dev.set_signed_currents(c_vec) + time.sleep(self.SETTLE_TIME) + + # Get coil constant + field_diff_mag = np.linalg.norm(g.MAGNETOMETER.field - ambient_field) + k_samples.append(field_diff_mag / c) + + # Average samples for axis + coil_constants[i] = np.average(k_samples) + k_deviations[i], _ = self.calculate_standard_deviation(k_samples) + + # Put device into an off and ready state + self.cage_dev.idle() + + self.put_message('coil_constant_results', {'k': coil_constants, + 'k_dev': k_deviations}) + + @staticmethod + def calculate_standard_deviation(data): + n = len(data) + average = 0 + for datapoint in data: + average += datapoint / n + std_dev = 0 + deviations = [] + for datapoint in data: + std_dev += (datapoint - average) ** 2 + deviations.append(datapoint - average) + std_dev = math.sqrt(std_dev / n) + return std_dev, deviations def put_message(self, command, arg): self.view_queue.put({'cmd': command, 'arg': arg}) \ No newline at end of file diff --git a/src/user_interface.py b/src/user_interface.py index d7e6914..793f0b0 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -23,7 +23,7 @@ import src.globals as g import src.csv_threading as csv import src.config_handling as config import src.csv_logging as log -from src.calibration import AmbientFieldCalibration +from src.calibration import AmbientFieldCalibration, CoilConstantCalibration from src.exceptions import DeviceAccessError from src.utility import ui_print import src.helmholtz_cage_device as helmholtz_cage_device @@ -547,9 +547,13 @@ class CalibrateAmbientField(Frame): StringVar(value="No data"), StringVar(value="No data")] self.calibration_procedure_progress_var = IntVar(value=0) + # Contains results for ambient field calibration self.ambient_field_result_vars = [StringVar(), StringVar(), StringVar()] self.ambient_field_ut_result_vars = [StringVar(), StringVar(), StringVar()] self.ambient_field_residual_vars = [StringVar(), StringVar(), StringVar()] + # Contains results for coil constant calibration + self.coil_constant_vars = [StringVar(), StringVar(), StringVar()] + self.coil_constant_dev_vars = [StringVar(), StringVar(), StringVar()] row_counter = 0 @@ -648,6 +652,36 @@ class CalibrateAmbientField(Frame): axis_data.grid(row=3, column=i+1, padx=5, pady=5, sticky="nw") ambient_field_residual_unit = Label(ambient_field_results_frame, text="\u03BCT") ambient_field_residual_unit.grid(row=3, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + + # Coil constant results + coil_constants_results_frame = LabelFrame(self.right_column, text="Coil Constants") + coil_constants_results_frame.grid(row=row_counter, column=1, padx=(100, 0), pady=20, sticky="nw") + for i, label in enumerate(['X', 'Y', 'Z']): + axis_label = Label(coil_constants_results_frame, text=label) + axis_label.grid(row=0, column=i + 1, padx=5, pady=5, sticky="nw") + # Coil constants + coil_constants_results_label = Label(coil_constants_results_frame, text="K:") + coil_constants_results_label.grid(row=1, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(coil_constants_results_frame, + textvariable=self.coil_constant_vars[i], + width=15, + state='readonly') + axis_data.grid(row=1, column=i + 1, padx=5, pady=5, sticky="nw") + coil_constants_results_unit = Label(coil_constants_results_frame, text="\u03BCT/A") + coil_constants_results_unit.grid(row=1, column=4, padx=5, pady=5, sticky="nw") + # Standard deviation of coil constants + coil_constants_dev_results_label = Label(coil_constants_results_frame, text="K Std. Dev.:") + coil_constants_dev_results_label.grid(row=2, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(coil_constants_results_frame, + textvariable=self.coil_constant_dev_vars[i], + width=15, + state='readonly') + axis_data.grid(row=2, column=i + 1, padx=5, pady=5, sticky="nw") + coil_constants_dev_results_unit = Label(coil_constants_results_frame, text="\u03BCT/A") + coil_constants_dev_results_unit.grid(row=2, column=4, padx=5, pady=5, sticky="nw") # This starts an endless polling loop self.update_view() @@ -686,6 +720,8 @@ class CalibrateAmbientField(Frame): self.calibration_procedure_progress_var.set(min(int(arg*100), 100)) elif cmd == 'ambient_data': self.update_ambient_calibration_results(arg) + elif cmd == 'coil_constant_results': + self.update_coil_constant_results(arg) else: ui_print("Error: Unexpected mpi command '{}' in CalibrationTool".format(cmd)) except queue.Empty: @@ -708,6 +744,12 @@ class CalibrateAmbientField(Frame): self.ambient_field_ut_result_vars[i].set("{:.3f}".format(results['ambient_ut'][i])) self.ambient_field_residual_vars[i].set("{:.3f}".format(results['residual'][i] * 1e6)) + def update_coil_constant_results(self, results): + for i in range(3): + # Remember to convert from T/A to microT/A + self.coil_constant_vars[i].set("{:.3f}".format(results['k'][i] * 1e6)) + self.coil_constant_dev_vars[i].set("{:.3f}".format(results['k_dev'][i] * 1e6)) + def calibration_procedure_ambient(self): try: self.calibration_ambient_thread = AmbientFieldCalibration(self.view_mpi_queue) @@ -719,7 +761,7 @@ class CalibrateAmbientField(Frame): def calibration_procedure_coil_constants(self): try: - self.calibration_coil_constants_thread = AmbientFieldCalibration(self.view_mpi_queue) + self.calibration_coil_constants_thread = CoilConstantCalibration(self.view_mpi_queue) self.calibration_coil_constants_thread.start() self.start_k_calibration_button.configure(text="Running") self.deactivate_buttons()