diff --git a/src/calibration.py b/src/calibration.py index b6aca63..2d52b04 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -1,4 +1,4 @@ -import math +from math import pi, sqrt import time from datetime import datetime from threading import Thread @@ -139,11 +139,19 @@ class CoilConstantCalibration(Thread): def calibration_procedure(self): # 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 + + # This generates linearly spaced current setpoints and excludes zero currents = np.linspace(-self.MEASUREMENT_RANGE, self.MEASUREMENT_RANGE, self.MEASUREMENT_POINTS * 2 + 1) currents = np.delete(currents, self.MEASUREMENT_POINTS) + + # Stores three vectors that correspond to the x,y,z actuation field directions. + axis_field_directions = [] + + # Result variables coil_constants = np.zeros(3) k_deviations = np.zeros(3) + + # The coil constant must be determined for every axis for i in range(3): k_samples = [] for c_idx, c in enumerate(currents): @@ -160,6 +168,10 @@ class CoilConstantCalibration(Thread): field_diff_mag = np.linalg.norm(g.MAGNETOMETER.field - ambient_field) k_samples.append(field_diff_mag / c) + # Save vector as principal coil direction if it is the last sample with the largest positive current + if c_idx == currents.shape[0] - 1: + axis_field_directions.append(g.MAGNETOMETER.field - ambient_field) + # Average samples for axis coil_constants[i] = np.average(k_samples) k_deviations[i], _ = self.calculate_standard_deviation(k_samples) @@ -167,8 +179,12 @@ class CoilConstantCalibration(Thread): # Put device into an off and ready state self.cage_dev.idle() + angles = {'xy': self.angle_between(axis_field_directions[0], axis_field_directions[1]) * 180 / pi, + 'yz': self.angle_between(axis_field_directions[1], axis_field_directions[2]) * 180 / pi, + 'xz': self.angle_between(axis_field_directions[0], axis_field_directions[2]) * 180 / pi} self.put_message('coil_constant_results', {'k': coil_constants, - 'k_dev': k_deviations}) + 'k_dev': k_deviations, + 'angle': angles}) @staticmethod def calculate_standard_deviation(data): @@ -181,8 +197,15 @@ class CoilConstantCalibration(Thread): for datapoint in data: std_dev += (datapoint - average) ** 2 deviations.append(datapoint - average) - std_dev = math.sqrt(std_dev / n) + std_dev = sqrt(std_dev / n) return std_dev, deviations + @staticmethod + def angle_between(v1, v2): + """ Returns the angle in radians between vectors 'v1' and 'v2'""" + v1_u = v1 / np.linalg.norm(v1) + v2_u = v2 / np.linalg.norm(v2) + return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)) + 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 793f0b0..21a43f3 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -554,6 +554,7 @@ class CalibrateAmbientField(Frame): # Contains results for coil constant calibration self.coil_constant_vars = [StringVar(), StringVar(), StringVar()] self.coil_constant_dev_vars = [StringVar(), StringVar(), StringVar()] + self.coil_angle_vars = [StringVar(), StringVar(), StringVar()] row_counter = 0 @@ -682,6 +683,21 @@ class CalibrateAmbientField(Frame): 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") + # Inter-axis angle labels + for i, label in enumerate(['X-Y', 'Y-Z', 'X-Z']): + axis_label = Label(coil_constants_results_frame, text=label) + axis_label.grid(row=3, column=i + 1, padx=5, pady=5, sticky="nw") + # Inter-axis angles + coil_angles_label = Label(coil_constants_results_frame, text="Angles:") + coil_angles_label.grid(row=4, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(coil_constants_results_frame, + textvariable=self.coil_angle_vars[i], + width=15, + state='readonly') + axis_data.grid(row=4, column=i + 1, padx=5, pady=5, sticky="nw") + coil_angles_unit = Label(coil_constants_results_frame, text="°") + coil_angles_unit.grid(row=4, column=4, padx=5, pady=5, sticky="nw") # This starts an endless polling loop self.update_view() @@ -749,6 +765,10 @@ class CalibrateAmbientField(Frame): # 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)) + # Coil angles use dict access + self.coil_angle_vars[0].set("{:.2f}".format(results['angle']['xy'])) + self.coil_angle_vars[1].set("{:.2f}".format(results['angle']['yz'])) + self.coil_angle_vars[2].set("{:.2f}".format(results['angle']['xz'])) def calibration_procedure_ambient(self): try: @@ -757,7 +777,7 @@ class CalibrateAmbientField(Frame): self.start_ambient_calibration_button.configure(text="Running") self.deactivate_buttons() except DeviceAccessError as e: - print("Error starting calibration procedure: {}".format(e)) + messagebox.showwarning("Calibration failed", "Failed to start calibration:\n{}".format(e)) def calibration_procedure_coil_constants(self): try: @@ -766,7 +786,7 @@ class CalibrateAmbientField(Frame): self.start_k_calibration_button.configure(text="Running") self.deactivate_buttons() except DeviceAccessError as e: - print("Error starting calibration procedure: {}".format(e)) + messagebox.showwarning("Calibration failed", "Failed to start calibration:\n{}".format(e)) class HardwareConfiguration(Frame):