diff --git a/requirements.txt b/requirements.txt index 7282e2f..a5a0817 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,6 @@ python-dateutil==2.8.2 pytz==2021.3 scipy==1.7.1 six==1.16.0 + +numpy~=1.19.3 +screeninfo~=0.8.1 \ No newline at end of file diff --git a/src/calibration.py b/src/calibration.py index 711cbd3..ceb9c74 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -239,12 +239,13 @@ class CoilConstantCalibration(Thread): class MagnetometerCalibrationSimple(Thread): - TEST_VECTOR_MAGNITUDE = 100e-6 # In Tesla. Chosen so it can be achieved with a 3A PSU. - def __init__(self, view_queue, calibration_points, calibration_interval, mgm_to_helmholtz_cos_trans): + def __init__(self, view_queue, calibration_points, calibration_interval, calibration_mag_field, + mgm_to_helmholtz_cos_trans): Thread.__init__(self) self.view_queue = view_queue self.calibration_points = calibration_points + self.calibration_mag_field = calibration_mag_field self.calibration_interval = calibration_interval self.matrix_trans_mgm_to_hh = [[x.get() for x in row] for row in mgm_to_helmholtz_cos_trans] @@ -305,7 +306,7 @@ class MagnetometerCalibrationSimple(Thread): # Collect sensor data for each test vector for vec_idx, test_vec in enumerate(test_vectors): # Command output - applied_vec = test_vec * self.TEST_VECTOR_MAGNITUDE + applied_vec = test_vec * self.calibration_mag_field self.cage_dev.set_field_raw(applied_vec) # Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings @@ -405,13 +406,14 @@ class MagnetometerCalibrationSimple(Thread): class MagnetometerCalibrationComplete(Thread): - TEST_VECTOR_MAGNITUDE = 100e-6 # In Tesla. Chosen so it can be achieved with a 3A PSU. - def __init__(self, view_queue, calibration_points, calibration_interval, mgm_to_helmholtz_cos_trans, right_column): + def __init__(self, view_queue, calibration_points, calibration_interval, calibration_mag_field, + mgm_to_helmholtz_cos_trans, right_column): Thread.__init__(self) self.view_queue = view_queue self.calibration_points = calibration_points self.calibration_interval = calibration_interval + self.calibration_mag_field = calibration_mag_field self.matrix_trans_mgm_to_hh = [[x.get() for x in row] for row in mgm_to_helmholtz_cos_trans] self.right_column = right_column @@ -443,7 +445,7 @@ class MagnetometerCalibrationComplete(Thread): def calibration_procedure(self): # According to method outlined in: # Chi, C. & Janosek, Lv, J-W., Wang, D (2018). Calibration of triaxial magnetometer with ellipsoid - # fitting method, DOI:10.1088/1755-1315/237/3/032015 + # fitting method, DOI:10.1088/1755-1315/237/3/032015 and https://github.com/nliaudat/magnetometer_calibration # This contains the raw experiment data for exporting # Each row is a dict containing the applied vector and measured vector @@ -472,7 +474,7 @@ class MagnetometerCalibrationComplete(Thread): # Collect sensor data for each test vector for vec_idx, test_vec in enumerate(test_vectors): # Command output - applied_vec = test_vec * self.TEST_VECTOR_MAGNITUDE + applied_vec = test_vec * self.calibration_mag_field self.cage_dev.set_field_raw(applied_vec) # Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings @@ -523,7 +525,7 @@ class MagnetometerCalibrationComplete(Thread): matrix_trans_mgm_to_hh_np = np.zeros((3, 3)) for row in range(3): for col in range(3): - matrix_trans_mgm_to_hh_np[row][col] = matrix_trans_mgm_to_hh_tk[row][col].get() + matrix_trans_mgm_to_hh_np[row][col] = matrix_trans_mgm_to_hh_tk[row][col] # matrix_trans_mgm_to_hh_np = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]] # hardcoded for MGM 1 / 3 # matrix_trans_mgm_to_hh_np = [[0, 1, 0], [-1, 0, 0], [0, 0, 1]] # hardcoded for MGM 0 / 2 ui_print("Applying transformation matrix to data. h_{set,mgm} = mgm_T_hh * h_{set,hh}") @@ -572,11 +574,7 @@ class MagnetometerCalibrationComplete(Thread): # Retrieve calibration parameters q_mat_inv = np.linalg.inv(q_mat) b = -np.dot(q_mat_inv, n) -<<<<<<< Updated upstream - a_mat_inv = np.real(1 / csqrt(np.dot(n.T, np.dot(q_mat_inv, n)) - d) * linalg_scipy.sqrtm(q_mat)) -======= a_mat_inv = np.real(mag_amp_avg_set / np.sqrt(np.dot(n.T, np.dot(q_mat_inv, n)) - d) * scipy.linalg.sqrtm(q_mat)) ->>>>>>> Stashed changes a_mat = np.linalg.inv(a_mat_inv) # Calculate error cal_x = np.zeros(mag_x_m.shape) @@ -586,14 +584,14 @@ class MagnetometerCalibrationComplete(Thread): for i in range(len(mag_x_m)): h = np.array([[mag_x_m[i], mag_y_m[i], mag_z_m[i]]]).T h_hat = np.matmul(a_mat_inv, h - b) # Scaling issue - cal_x[i] = h_hat[0] * mag_amp_avg_set - cal_y[i] = h_hat[1] * mag_amp_avg_set - cal_z[i] = h_hat[2] * mag_amp_avg_set + cal_x[i] = h_hat[0] + cal_y[i] = h_hat[1] + cal_z[i] = h_hat[2] mag = np.dot(h_hat.T, h_hat) err = (mag[0][0] - 1) ** 2 total_error += err # Scale unity solution back to original scaling - a_mat_inv = a_mat_inv * mag_amp_avg_set + a_mat_inv = a_mat_inv # Console output ui_print("Average magnitude: mag_amp_avg_set = {:.4f} uT, mag_amp_avg_m = {:.4f} uT".format( mag_amp_avg_set * 10 ** 6, mag_amp_avg_m * 10 ** 6)) @@ -694,7 +692,7 @@ class MagnetometerCalibrationComplete(Thread): return mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m @staticmethod - def fit_ellipsoid(mag_x_m, mag_y_m, mag_z_m): + def fit_ellipsoid_old(mag_x_m, mag_y_m, mag_z_m): # Script is adapted version from ThePoorEngineer - Calibrating the magnetometer # https://thepoorengineer.com/en/calibrating-the-magnetometer/#Calibration a1 = mag_x_m ** 2 @@ -749,10 +747,75 @@ class MagnetometerCalibrationComplete(Thread): return q_mat, n, d @staticmethod -<<<<<<< Updated upstream - def plot_magnetometer_calibration(target_column, mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m, cal_x, cal_y, - cal_z, mag_amp_avg_set): -======= + def fit_ellipsoid(mag_x_m, mag_y_m, mag_z_m): + """ Estimate ellipsoid parameters from a set of points. + Parameters + ---------- + mag_x_m, mag_y_m, mag_z_m : array_like, array_like, array_like + The samples (M,N) where M=3 (x,y,z) and N=number of samples. + Returns + ------- + s : array_like + The samples (M,N) where M=3 (x,y,z) and N=number of samples. + Returns + ------- + M, n, d : array_like, array_like, float + The ellipsoid parameters M, n, d. + References + ---------- + .. [1] Qingde Li; Griffiths, J.G., "Least squares ellipsoid specific + fitting," in Geometric Modeling and Processing, 2004. + Proceedings, vol., no., pp.335-340, 2004 + .. https://github.com/nliaudat/magnetometer_calibration/blob/main/calibrate.py + """ + + # Converts to samples (M,N) where M=3 (x,y,z) and N=number of samples. + s = np.array([mag_x_m, mag_y_m, mag_z_m]) + + # d (samples) + d = np.array([s[0] ** 2., s[1] ** 2., s[2] ** 2., + 2. * s[1] * s[2], 2. * s[0] * s[2], 2. * s[0] * s[1], + 2. * s[0], 2. * s[1], 2. * s[2], np.ones_like(s[0])]) + + # s, s_11, s_12, s_21, s_22 (eq. 11) + s = np.dot(d, d.T) + s_11 = s[:6, :6] + s_12 = s[:6, 6:] + s_21 = s[6:, :6] + s_22 = s[6:, 6:] + + # c (Eq. 8, k=4) + c = np.array([[-1, 1, 1, 0, 0, 0], + [1, -1, 1, 0, 0, 0], + [1, 1, -1, 0, 0, 0], + [0, 0, 0, -4, 0, 0], + [0, 0, 0, 0, -4, 0], + [0, 0, 0, 0, 0, -4]]) + + # v_1 (eq. 15, solution) + e = np.dot(np.linalg.inv(c), + s_11 - np.dot(s_12, np.dot(np.linalg.inv(s_22), s_21))) + + e_w, e_v = np.linalg.eig(e) + + v_1 = e_v[:, np.argmax(e_w)] + if v_1[0] < 0: + v_1 = -v_1 + + # v_2 (eq. 13, solution) + v_2 = np.dot(np.dot(-np.linalg.inv(s_22), s_21), v_1) + + # Quadratic-form parameters + m = np.array([[v_1[0], v_1[3], v_1[4]], + [v_1[3], v_1[1], v_1[5]], + [v_1[4], v_1[5], v_1[2]]]) + n = np.array([[v_2[0]], + [v_2[1]], + [v_2[2]]]) + d = v_2[3] + + return m, n, d + def fit_ellipsoid(mag_x_m, mag_y_m, mag_z_m): """ Estimate ellipsoid parameters from a set of points. Parameters @@ -825,7 +888,6 @@ class MagnetometerCalibrationComplete(Thread): @staticmethod def plot_magnetometer_calibration(target_column, mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m, cal_x, cal_y, cal_z, mag_amp_avg_set): ->>>>>>> Stashed changes plot_fontsize = 5 ax_width = 0.2 diff --git a/src/exceptions.py b/src/exceptions.py index f1003d2..7e73f0f 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -11,3 +11,8 @@ class DeviceAccessError(Exception): class DeviceBusy(DeviceAccessError): """Error thrown when the HW proxy (i.e. access) cannot be acquired""" pass + + +class MagFieldOutOfBounds(Exception): + """Set magnetic field must be a positive number between 0 and 200µT!""" + pass diff --git a/src/globals.py b/src/globals.py index cfae625..89bdac6 100644 --- a/src/globals.py +++ b/src/globals.py @@ -50,3 +50,6 @@ default_psu_config = { # Configuration for socket interface SOCKET_PORT = 6677 SOCKET_MAX_CONNECTIONS = 5 + +# Hardware safety limits +MAG_MAG_FIELD = 5*37*1e-6 # [µT=A*µT/A] min coil constant x PSU current limit diff --git a/src/user_interface.py b/src/user_interface.py index 1f43590..5abea0d 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -8,6 +8,7 @@ from tkinter import * from tkinter import ttk from tkinter import messagebox from tkinter import filedialog +import screeninfo from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt @@ -25,14 +26,14 @@ import src.config_handling as config import src.csv_logging as log from src.calibration import AmbientFieldCalibration, CoilConstantCalibration, MagnetometerCalibrationSimple, \ MagnetometerCalibrationComplete -from src.exceptions import DeviceAccessError +from src.exceptions import DeviceAccessError, MagFieldOutOfBounds from src.utility import ui_print, save_dict_list_to_csv, load_dict_list_from_csv import src.helmholtz_cage_device as helmholtz_cage_device # Define global font styles: screen_height_limit1 = 1100 # Limit after which font size gets reduced screen_height_limit2 = 900 # Limit after which font size gets reduced -points = [13, 11, 9, 7, 6, 5] +points = [15, 13, 11, 9, 7, 6] font = "Arial" HEADER_FONT = (font, points[0], "bold") SUB_HEADER_FONT = (font, points[2], "bold") @@ -53,22 +54,30 @@ class HelmholtzGUI(Tk): except TclError: pass + # Check if multiple monitors connected and open the window on the largest or the non-primary monitor + monitors = screeninfo.get_monitors() + [sc_no, sc_x_max, sc_y_max, sc_width_max, sc_height_max] = [0, monitors[0].x, monitors[0].y, + monitors[0].width, monitors[0].height] + for i in range(len(monitors)): + if sc_width_max <= monitors[i].width: + [sc_no, sc_x_max, sc_y_max, sc_width_max, sc_height_max] = [i, monitors[i].x, monitors[i].y, + monitors[i].width, monitors[i].height] + print("Opening console on largest screen: #{} with {} x {}".format(sc_no, sc_width_max, sc_height_max)) + Tk.geometry(self, "{}x{}+{}+{}".format(sc_width_max, sc_height_max, sc_x_max, sc_y_max)) + Tk.state(self, 'zoomed') # Change default fonts if display resolution is too small - screen_width = Tk.winfo_screenwidth(self) - screen_height = Tk.winfo_screenheight(self) - print("Screensize: {} x {}".format(screen_width, screen_height)) - if screen_height <= screen_height_limit2: + if sc_height_max <= screen_height_limit2: red = 2 - elif screen_height <= screen_height_limit1: + elif sc_height_max <= screen_height_limit1: red = 1 else: red = 0 global HEADER_FONT, SUB_HEADER_FONT, BIG_BUTTON_FONT, SMALL_BUTTON_FONT, DEFAULT_FONT - HEADER_FONT = (font, points[0+red], "bold") - SUB_HEADER_FONT = (font, points[2+red], "bold") - BIG_BUTTON_FONT = (font, points[1+red], "bold") - SMALL_BUTTON_FONT = (font, points[2+red]) - DEFAULT_FONT = (font, points[2+red]) + HEADER_FONT = (font, points[0 + red], "bold") + SUB_HEADER_FONT = (font, points[2 + red], "bold") + BIG_BUTTON_FONT = (font, points[1 + red], "bold") + SMALL_BUTTON_FONT = (font, points[2 + red]) + DEFAULT_FONT = (font, points[2 + red]) self.option_add("*font", DEFAULT_FONT) self.Menu = TopMenu(self) # display dropdown menu bar at the top (see TopMenu class for details) @@ -89,7 +98,8 @@ class HelmholtzGUI(Tk): main_area = Frame(self, padx=10, pady=10) # create main area Frame where controls of each mode are displayed main_area.pack(side="top", fill="both", expand=True) # pack main area at the top of the window - main_area.grid_rowconfigure(0, weight=5, minsize=800) # configure rows and columns of the Tkinter grid to expand with window + main_area.grid_rowconfigure(0, weight=5, + minsize=800) # configure rows and columns of the Tkinter grid to expand with window main_area.grid_columnconfigure(0, weight=1) # initialize the GUI pages for the different modes and setup switching between them @@ -253,17 +263,17 @@ class ManualMode(Frame): # add button for executing the current entries execute_button = Button(self.buttons_frame, text="Execute", command=self.execute, - pady=5, padx=5, font=BIG_BUTTON_FONT) + pady=5, padx=5, font=SMALL_BUTTON_FONT) execute_button.grid(row=row_counter, column=0, padx=5) # add button for quick power_down power_down_button = Button(self.buttons_frame, text="Power Down All", command=self.power_down, - pady=5, padx=5, font=BIG_BUTTON_FONT) + pady=5, padx=5, font=SMALL_BUTTON_FONT) power_down_button.grid(row=row_counter, column=1, padx=5) # add button for reinitialization of devices reinit_button = Button(self.buttons_frame, text="Reinitialize", command=self.reinitialize, - pady=5, padx=5, font=BIG_BUTTON_FONT) + pady=5, padx=5, font=SMALL_BUTTON_FONT) reinit_button.grid(row=row_counter, column=2, padx=5) row_counter = row_counter + 1 @@ -578,8 +588,6 @@ class CalibrateAmbientField(Frame): self.controller = controller # To center window - # self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) self.left_column = Frame(self) self.left_column.grid(row=0, column=0, sticky="nsew") self.right_column = Frame(self) @@ -645,7 +653,7 @@ class CalibrateAmbientField(Frame): field_data_axis_units.grid(row=i, column=3, padx=5, pady=3) row_counter += 1 - # Calibration start and save to csv buttons + # Calibration start and reinitialize buttons start_button_frame = Frame(self.left_column) start_button_frame.grid(row=row_counter, column=0, sticky="sw") self.start_ambient_calibration_button = Button(start_button_frame, text="Calibrate Ambient Field", @@ -656,6 +664,11 @@ class CalibrateAmbientField(Frame): command=self.calibration_procedure_coil_constants, pady=5, padx=5, font=SMALL_BUTTON_FONT) self.start_k_calibration_button.grid(row=0, column=1, padx=10, pady=10) + # Reinitialize button + self.reinitialize_button = Button(start_button_frame, text="Reinitialize", + command=self.reinitialize, + pady=5, padx=5, font=SMALL_BUTTON_FONT) + self.reinitialize_button.grid(row=0, column=2, padx=10, pady=10) row_counter += 1 # Calibration progress bar @@ -969,6 +982,15 @@ class CalibrateAmbientField(Frame): self.clipboard_append(self.coil_constant_clipboard) self.update() + def reinitialize(self): # called on "Reinitialize!" button press + # reinitialize all PSUs and the Arduino + g.CAGE_DEVICE.reconnect_hardware_async() + + # log change to the log file if user has selected event logging in the Configure Logging window + logger = self.controller.pages[ConfigureLogging] # get object of logging configurator + if logger.event_logging: # data should be logged when test bench is commanded + logger.log_datapoint() # log data + class CalibrateMagnetometerSimple(Frame): def __init__(self, parent, controller): @@ -997,6 +1019,7 @@ class CalibrateMagnetometerSimple(Frame): self.calibration_procedure_progress_var = IntVar(value=0) # Calibration parameters self.calibration_points_var = IntVar(value=8) + self.mag_field_magnitude_var = DoubleVar(value=100) self.calibration_interval_var = DoubleVar(value=5) # Calibration results self.sensitivity_result_vars = [StringVar(), StringVar(), StringVar()] @@ -1048,26 +1071,51 @@ class CalibrateMagnetometerSimple(Frame): # Centered controls controls_frame = Frame(self.left_column) controls_frame.grid(row=row_counter, column=0, sticky="nw") + # Magnitude of magnetic field + mag_field_magnitude_label = Label(controls_frame, text="Magnetic field magnitude") + mag_field_magnitude_label.grid(row=0, column=0, pady=5, sticky="nw") + mag_field_magnitude_entry = Entry(controls_frame, textvariable=self.mag_field_magnitude_var) + mag_field_magnitude_entry.grid(row=0, column=1, pady=5, sticky="nw") + mag_field_magnitude_unit = Label(controls_frame, text="µT (0 to {:.0f} µT)".format(g.MAG_MAG_FIELD * 1e6)) + mag_field_magnitude_unit.grid(row=0, column=2, pady=5, sticky="nw") # Number of calibration points calibration_point_nr_label = Label(controls_frame, text="# of calibration points") - calibration_point_nr_label.grid(row=0, column=0, pady=5, sticky="nw") + calibration_point_nr_label.grid(row=1, column=0, pady=5, sticky="nw") calibration_point_nr_entry = Entry(controls_frame, textvariable=self.calibration_points_var) - calibration_point_nr_entry.grid(row=0, column=1, pady=5, sticky="nw") + calibration_point_nr_entry.grid(row=1, column=1, pady=5, sticky="nw") + calibration_point_nr__unit = Label(controls_frame, text="- (> 8 points)") + calibration_point_nr__unit.grid(row=1, column=2, pady=5, sticky="nw") # Measurement interval calibration_point_nr_label = Label(controls_frame, text="Measurement interval [s]") - calibration_point_nr_label.grid(row=1, column=0, pady=5, sticky="nw") + calibration_point_nr_label.grid(row=2, column=0, pady=5, sticky="nw") calibration_point_nr_entry = Entry(controls_frame, textvariable=self.calibration_interval_var) - calibration_point_nr_entry.grid(row=1, column=1, pady=5, sticky="nw") + calibration_point_nr_entry.grid(row=2, column=1, pady=5, sticky="nw") + calibration_point_nr_unit = Label(controls_frame, text="s (> 2 s)") + calibration_point_nr_unit.grid(row=2, column=2, pady=5, sticky="nw") + # Measurement interval + calibration_point_nr_label = Label(controls_frame, text="Measurement interval [s]") + calibration_point_nr_label.grid(row=2, column=0, pady=5, sticky="nw") + calibration_point_nr_entry = Entry(controls_frame, textvariable=self.calibration_interval_var) + calibration_point_nr_entry.grid(row=2, column=1, pady=5, sticky="nw") + calibration_point_nr_unit = Label(controls_frame, text="s (> 2 s)") + calibration_point_nr_unit.grid(row=2, column=2, pady=5, sticky="nw") # Calibration start buttons start_button_frame = Frame(controls_frame) - start_button_frame.grid(row=2, column=0, columnspan=2) + start_button_frame.grid(row=3, column=0, columnspan=1, sticky="nw") self.start_calibration_button = Button(start_button_frame, text="Start Calibration", command=self.start_calibration_procedure, pady=5, padx=5, font=SMALL_BUTTON_FONT) - self.start_calibration_button.grid(row=0, column=0, padx=10, pady=(30, 10)) + self.start_calibration_button.grid(row=0, column=0, padx=10, pady=(30, 10), sticky="we") + # Reinitialize button + reinitialize_button_frame = Frame(controls_frame) + reinitialize_button_frame.grid(row=3, column=1, columnspan=1) + self.reinitialize_button = Button(reinitialize_button_frame, text="Reinitialize", + command=self.reinitialize, + pady=5, padx=5, font=SMALL_BUTTON_FONT) + self.reinitialize_button.grid(row=0, column=0, padx=10, pady=(30, 10), sticky="we") # Calibration progress bar progress_bar_frame = Frame(controls_frame) - progress_bar_frame.grid(row=3, column=0, columnspan=2) + progress_bar_frame.grid(row=4, column=0, columnspan=2) calibration_procedure_progress_label = Label(progress_bar_frame, text="Progress:") calibration_procedure_progress_label.grid(row=0, column=0, padx=10, pady=10, sticky="nw") calibration_procedure_progress = ttk.Progressbar(progress_bar_frame, @@ -1325,12 +1373,18 @@ class CalibrateMagnetometerSimple(Frame): try: calibration_points = self.calibration_points_var.get() calibration_interval = self.calibration_interval_var.get() + calibration_mag_field = self.mag_field_magnitude_var.get() * 1e-6 # converted to micro Tesla + if calibration_mag_field <= 0 or calibration_mag_field > g.MAG_MAG_FIELD: + raise MagFieldOutOfBounds self.calibration_thread = MagnetometerCalibrationSimple(self.view_mpi_queue, calibration_points, calibration_interval, + calibration_mag_field, self.mgm_to_helmholtz_cos_trans) self.calibration_thread.start() self.deactivate_buttons() + except MagFieldOutOfBounds as e: + messagebox.showwarning("Calibration failed", "\n{}".format(e)) except (DeviceAccessError, TclError) as e: messagebox.showwarning("Calibration failed", "Failed to start calibration:\n{}".format(e)) @@ -1409,6 +1463,15 @@ class CalibrateMagnetometerSimple(Frame): [DoubleVar(value=0), DoubleVar(value=1), DoubleVar(value=0)], [DoubleVar(value=0), DoubleVar(value=0), DoubleVar(value=1)]] + def reinitialize(self): # called on "Reinitialize!" button press + # reinitialize all PSUs and the Arduino + g.CAGE_DEVICE.reconnect_hardware_async() + + # log change to the log file if user has selected event logging in the Configure Logging window + logger = self.controller.pages[ConfigureLogging] # get object of logging configurator + if logger.event_logging: # data should be logged when test bench is commanded + logger.log_datapoint() # log data + class CalibrateMagnetometerComplete(Frame): def __init__(self, parent, controller): @@ -1438,6 +1501,7 @@ class CalibrateMagnetometerComplete(Frame): StringVar(value="No data")] self.calibration_procedure_progress_var = IntVar(value=0) # Calibration parameters + self.mag_field_magnitude_var = DoubleVar(value=100) self.calibration_points_var = IntVar(value=8) self.calibration_interval_var = DoubleVar(value=5) # Calibration results @@ -1500,26 +1564,44 @@ class CalibrateMagnetometerComplete(Frame): # Centered controls controls_frame = Frame(self.left_column) controls_frame.grid(row=row_counter, column=0, sticky="nw") + # Magnitude of magnetic field + mag_field_magnitude_label = Label(controls_frame, text="Magnetic field magnitude") + mag_field_magnitude_label.grid(row=0, column=0, pady=5, sticky="nw") + mag_field_magnitude_entry = Entry(controls_frame, textvariable=self.mag_field_magnitude_var) + mag_field_magnitude_entry.grid(row=0, column=1, pady=5, sticky="nw") + mag_field_magnitude_unit = Label(controls_frame, text="µT (0 to {:.0f} µT)".format(g.MAG_MAG_FIELD * 1e6)) + mag_field_magnitude_unit.grid(row=0, column=2, pady=5, sticky="nw") # Number of calibration points calibration_point_nr_label = Label(controls_frame, text="# of calibration points") - calibration_point_nr_label.grid(row=0, column=0, pady=5, sticky="nw") + calibration_point_nr_label.grid(row=1, column=0, pady=5, sticky="nw") calibration_point_nr_entry = Entry(controls_frame, textvariable=self.calibration_points_var) - calibration_point_nr_entry.grid(row=0, column=1, pady=5, sticky="nw") + calibration_point_nr_entry.grid(row=1, column=1, pady=5, sticky="nw") + calibration_point_nr__unit = Label(controls_frame, text="- (> 8 points)") + calibration_point_nr__unit.grid(row=1, column=2, pady=5, sticky="nw") # Measurement interval calibration_point_nr_label = Label(controls_frame, text="Measurement interval [s]") - calibration_point_nr_label.grid(row=1, column=0, pady=5, sticky="nw") + calibration_point_nr_label.grid(row=2, column=0, pady=5, sticky="nw") calibration_point_nr_entry = Entry(controls_frame, textvariable=self.calibration_interval_var) - calibration_point_nr_entry.grid(row=1, column=1, pady=5, sticky="nw") + calibration_point_nr_entry.grid(row=2, column=1, pady=5, sticky="nw") + calibration_point_nr_unit = Label(controls_frame, text="s (> 2 s)") + calibration_point_nr_unit.grid(row=2, column=2, pady=5, sticky="nw") # Calibration start buttons start_button_frame = Frame(controls_frame) - start_button_frame.grid(row=2, column=0, columnspan=2, sticky="nw") + start_button_frame.grid(row=3, column=0, columnspan=1, sticky="nw") self.start_calibration_button = Button(start_button_frame, text="Start Calibration", command=self.start_calibration_procedure, pady=5, padx=5, font=SMALL_BUTTON_FONT) self.start_calibration_button.grid(row=0, column=0, padx=10, pady=(30, 10), sticky="we") + # Reinitialize button + reinitialize_button_frame = Frame(controls_frame) + reinitialize_button_frame.grid(row=3, column=1, columnspan=1) + self.reinitialize_button = Button(reinitialize_button_frame, text="Reinitialize", + command=self.reinitialize, + pady=5, padx=5, font=SMALL_BUTTON_FONT) + self.reinitialize_button.grid(row=0, column=0, padx=10, pady=(30, 10), sticky="we") # Calibration progress bar progress_bar_frame = Frame(controls_frame) - progress_bar_frame.grid(row=3, column=0, columnspan=2) + progress_bar_frame.grid(row=4, column=0, columnspan=2) calibration_procedure_progress_label = Label(progress_bar_frame, text="Progress:") calibration_procedure_progress_label.grid(row=0, column=0, padx=10, pady=10, sticky="nw") calibration_procedure_progress = ttk.Progressbar(progress_bar_frame, @@ -1611,11 +1693,8 @@ class CalibrateMagnetometerComplete(Frame): width=15, state='readonly') axis_data.grid(row=row_counter + row, column=1 + column, padx=5, pady=5, sticky="nw") -<<<<<<< Updated upstream -======= results_label_unit = Label(calibration_results_frame, text="-") results_label_unit.grid(row=row_counter + row, column=1 + 3, padx=5, pady=5, sticky="nw") ->>>>>>> Stashed changes row_counter += 3 """ # A_mat @@ -1639,11 +1718,8 @@ class CalibrateMagnetometerComplete(Frame): width=15, state='readonly') axis_data.grid(row=row_counter, column=1 + row, padx=5, pady=5, sticky="nw") -<<<<<<< Updated upstream -======= results_label_unit = Label(calibration_results_frame, text="T") results_label_unit.grid(row=row_counter, column=1 + row + 1, padx=5, pady=5, sticky="nw") ->>>>>>> Stashed changes row_counter += 1 # Save calibration buttons @@ -1803,23 +1879,15 @@ class CalibrateMagnetometerComplete(Frame): try: calibration_points = self.calibration_points_var.get() calibration_interval = self.calibration_interval_var.get() -<<<<<<< Updated upstream - self.calibration_thread = MagnetometerCalibrationComplete(self.view_mpi_queue, - calibration_points, - calibration_interval, - self.mgm_to_helmholtz_cos_trans, - self.right_column) - self.calibration_thread.start() - self.deactivate_buttons() -======= calibration_mag_field = self.mag_field_magnitude_var.get() * 1e-6 # converted to micro Tesla if calibration_mag_field <= 0 or calibration_mag_field > g.MAG_MAG_FIELD: raise MagFieldOutOfBounds - [self.calibration_thread, raw_data] = MagnetometerCalibrationComplete(self.view_mpi_queue, - calibration_points, - calibration_interval, - calibration_mag_field, - self.right_column) + self.calibration_thread = MagnetometerCalibrationComplete(self.view_mpi_queue, + calibration_points, + calibration_interval, + calibration_mag_field, + self.mgm_to_helmholtz_cos_trans, + self.right_column) self.calibration_thread.start() self.deactivate_buttons() # Use collected data to build and solve system of equations @@ -1832,7 +1900,6 @@ class CalibrateMagnetometerComplete(Frame): cal_x, cal_y, cal_z, mag_amp_avg_set) except MagFieldOutOfBounds as e: messagebox.showwarning("Calibration failed", "\n{}".format(e)) ->>>>>>> Stashed changes except (DeviceAccessError, TclError) as e: messagebox.showwarning("Calibration failed", "Failed to start calibration:\n{}".format(e)) @@ -1857,21 +1924,15 @@ class CalibrateMagnetometerComplete(Frame): return self.calibration_raw_results = raw_data + # Get transformation matrix from GUI and transform data + mgm_to_helmholtz_cos_trans = [[x.get() for x in row] for row in self.mgm_to_helmholtz_cos_trans] # Execute calibration function and display results sensor_parameters, mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m, cal_x, cal_y, cal_z, mag_amp_avg_set = MagnetometerCalibrationComplete.solve_system( -<<<<<<< Updated upstream - raw_data, self.mgm_to_helmholtz_cos_trans) - MagnetometerCalibrationComplete.plot_magnetometer_calibration(self.right_column, mag_x_set, mag_y_set, mag_z_set, - mag_x_m, - mag_y_m, mag_z_m, cal_x, cal_y, - cal_z, mag_amp_avg_set) -======= raw_data, mgm_to_helmholtz_cos_trans) MagnetometerCalibrationComplete.plot_magnetometer_calibration(self.right_column, mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m, cal_x, cal_y, cal_z, mag_amp_avg_set) ->>>>>>> Stashed changes # Pass results to UI self.put_message('calibration_data', {'results': sensor_parameters, 'raw_data': raw_data}) except TypeError: @@ -1972,6 +2033,15 @@ class CalibrateMagnetometerComplete(Frame): [DoubleVar(value=0), DoubleVar(value=1), DoubleVar(value=0)], [DoubleVar(value=0), DoubleVar(value=0), DoubleVar(value=1)]] + def reinitialize(self): # called on "Reinitialize!" button press + # reinitialize all PSUs and the Arduino + g.CAGE_DEVICE.reconnect_hardware_async() + + # log change to the log file if user has selected event logging in the Configure Logging window + logger = self.controller.pages[ConfigureLogging] # get object of logging configurator + if logger.event_logging: # data should be logged when test bench is commanded + logger.log_datapoint() # log data + class HardwareConfiguration(Frame): """Settings window to set program constants"""