diff --git a/src/user_interface.py b/src/user_interface.py index 3a56d2d..059f9cb 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -15,10 +15,8 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import numpy as np import os import os.path -import threading from datetime import datetime from math import pi -import csv # import other project files: import src.globals as g @@ -28,7 +26,7 @@ import src.csv_logging as log from src.calibration_simple import AmbientFieldCalibration, CoilConstantCalibration, MagnetometerCalibration from src.calibration_complete import AmbientFieldCalibration, CoilConstantCalibration, MagnetometerCalibration from src.exceptions import DeviceAccessError -from src.utility import ui_print, save_dict_list_to_csv +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 font styles: @@ -980,13 +978,12 @@ class CalibrateMagnetometerSimple(Frame): self.angle_to_plane_result_vars = [StringVar(), StringVar(), StringVar()] self.angle_in_plane_result_vars = [StringVar(), StringVar(), StringVar()] self.residual_result_vars = [StringVar(), StringVar(), StringVar()] - self.calibration_raw_results = None # Cached raw experiment data to allow for saving to csv. - self.clipboard = "" # Clipboard string containing results - self.cos_trans_matrix_clipboard = "" # Clipboard string containing coordinate transformation matrix - self.mgm_to_helmholtz_cos_trans = [[DoubleVar(value=1), DoubleVar(value=0), DoubleVar(value=0)], [DoubleVar(value=0), DoubleVar(value=1), DoubleVar(value=0)], [DoubleVar(value=0), DoubleVar(value=0), DoubleVar(value=1)]] + self.calibration_raw_results = None # Cached raw experiment data to allow for saving to csv. + self.clipboard = "" # Clipboard string containing results + self.cos_trans_matrix_clipboard = "" # Clipboard string containing coordinate transformation matrix # UI Elements row_counter = 0 @@ -1246,7 +1243,7 @@ class CalibrateMagnetometerSimple(Frame): elif cmd == 'progress': self.calibration_procedure_progress_var.set(min(int(arg * 100), 100)) elif cmd == 'calibration_data': - self.display_calibration_results(arg) + self.display_calibration_results_simple(arg) else: ui_print("Error: Unexpected mpi command '{}' in CalibrationTool".format(cmd)) except queue.Empty: @@ -1261,7 +1258,7 @@ class CalibrateMagnetometerSimple(Frame): def deactivate_buttons(self): self.start_calibration_button.configure(text="Running...", state=DISABLED) - def display_calibration_results(self, results): + def display_calibration_results_simple(self, results): # Cache raw experiment data for saving later self.calibration_raw_results = results['raw_data'] @@ -1415,11 +1412,20 @@ class CalibrateMagnetometerComplete(Frame): self.calibration_points_var = IntVar(value=8) self.calibration_interval_var = DoubleVar(value=5) # Calibration results - self.sensitivity_result_vars = [StringVar(), StringVar(), StringVar()] - self.offset_result_vars = [StringVar(), StringVar(), StringVar()] - self.angle_to_plane_result_vars = [StringVar(), StringVar(), StringVar()] - self.angle_in_plane_result_vars = [StringVar(), StringVar(), StringVar()] - self.residual_result_vars = [StringVar(), StringVar(), StringVar()] + # FLAG + self.a_mat_result_vars = [[DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()],] + self.a_mat_inv_result_vars = [[DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()],] + self.b_result_vars = [DoubleVar(), DoubleVar(), DoubleVar()] + self.q_mat_result_vars = [[DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()], + [DoubleVar(), DoubleVar(), DoubleVar()],] + self.n_result_vars = [DoubleVar(), DoubleVar(), DoubleVar()] + self.mag_amp_avg_set_result_vars = DoubleVar() + self.total_error_result_vars = DoubleVar() self.calibration_raw_results = None # Cached raw experiment data to allow for saving to csv. self.clipboard = "" # Clipboard string containing results self.cos_trans_matrix_clipboard = "" # Clipboard string containing coordinate transformation matrix @@ -1498,67 +1504,43 @@ class CalibrateMagnetometerComplete(Frame): row_counter = 0 calibration_results_frame = LabelFrame(self.right_column, text="Magnetometer Results") calibration_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(calibration_results_frame, text=label) - axis_label.grid(row=0, column=i + 1, padx=5, pady=5, sticky="nw") - # Axis sensitivities - sensitivity_results_label = Label(calibration_results_frame, text="Sensitivity:") - sensitivity_results_label.grid(row=1, column=0, padx=5, pady=5, sticky="nw") - for i in range(3): - axis_data = Entry(calibration_results_frame, - textvariable=self.sensitivity_result_vars[i], - width=15, - state='readonly') - axis_data.grid(row=1, column=i + 1, padx=5, pady=5, sticky="nw") - sensitivity_results_unit = Label(calibration_results_frame, text="-") - sensitivity_results_unit.grid(row=1, column=4, padx=5, pady=5, sticky="nw") - # Axis offsets - offset_results_label = Label(calibration_results_frame, text="Offset:") - offset_results_label.grid(row=2, column=0, padx=5, pady=5, sticky="nw") - for i in range(3): - axis_data = Entry(calibration_results_frame, - textvariable=self.offset_result_vars[i], - width=15, - state='readonly') - axis_data.grid(row=2, column=i + 1, padx=5, pady=5, sticky="nw") - offset_results_unit = Label(calibration_results_frame, text="\u03BCT") - offset_results_unit.grid(row=2, column=4, padx=5, pady=5, sticky="nw") - # Angle to XY coil plane - angle_to_plane_label = Label(calibration_results_frame, text="Angle to XY plane:") - angle_to_plane_label.grid(row=3, column=0, padx=5, pady=5, sticky="nw") - for i in range(3): - axis_data = Entry(calibration_results_frame, - textvariable=self.angle_to_plane_result_vars[i], - width=15, - state='readonly') - axis_data.grid(row=3, column=i + 1, padx=5, pady=5, sticky="nw") - angle_to_plane_unit = Label(calibration_results_frame, text="°") - angle_to_plane_unit.grid(row=3, column=4, padx=5, pady=5, sticky="nw") - # Angle in XY coil plane - angle_in_plane_label = Label(calibration_results_frame, text="Angle in XY plane:") - angle_in_plane_label.grid(row=4, column=0, padx=5, pady=5, sticky="nw") - for i in range(3): - axis_data = Entry(calibration_results_frame, - textvariable=self.angle_in_plane_result_vars[i], - width=15, - state='readonly') - axis_data.grid(row=4, column=i + 1, padx=5, pady=5, sticky="nw") - angle_in_plane_unit = Label(calibration_results_frame, text="°") - angle_in_plane_unit.grid(row=4, column=4, padx=5, pady=5, sticky="nw") - # Residual in system of equations - residual_label = Label(calibration_results_frame, text="Residual:") - residual_label.grid(row=5, column=0, padx=5, pady=5, sticky="nw") - for i in range(3): - axis_data = Entry(calibration_results_frame, - textvariable=self.residual_result_vars[i], - width=15, - state='readonly') - axis_data.grid(row=5, column=i + 1, padx=5, pady=5, sticky="nw") - residual_unit = Label(calibration_results_frame, text="\u03BCT") - residual_unit.grid(row=5, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + # A_mat_inv + results_label_a_mat_inv = Label(calibration_results_frame, text="A^-1 =") + results_label_a_mat_inv.grid(row=row_counter+1, column=0, padx=5, pady=5, sticky="nw") + for row in range(3): + for column in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.a_mat_inv_result_vars[row][column], + width=15, + state='readonly') + axis_data.grid(row=row_counter+row, column=i + column, padx=5, pady=5, sticky="nw") + row_counter += 3 + # A_mat + results_label_a_mat_inv = Label(calibration_results_frame, text="A =") + results_label_a_mat_inv.grid(row=row_counter+1, column=0, padx=5, pady=5, sticky="nw") + for row in range(3): + for column in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.a_mat_result_vars[row][column], + width=15, + state='readonly') + axis_data.grid(row=row_counter+row, column=i + column, padx=5, pady=5, sticky="nw") + row_counter += 3 + # b + results_label_a_mat_inv = Label(calibration_results_frame, text="b^T =") + results_label_a_mat_inv.grid(row=row_counter, column=0, padx=5, pady=5, sticky="nw") + for row in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.b_result_vars[row], #FLAG _result_vars does not compule + width=15, + state='readonly') + axis_data.grid(row=row_counter, column=i + row, padx=5, pady=5, sticky="nw") + row_counter += 1 + # Save calibration buttons save_calibration_results_frame = Frame(calibration_results_frame) - save_calibration_results_frame.grid(row=6, column=0, columnspan=5) + save_calibration_results_frame.grid(row=row_counter, column=0, columnspan=5) # Save and apply self.export_calibration_button = Button(save_calibration_results_frame, text="Export raw to CSV", command=self.export_csv_calibration_raw_results, @@ -1570,11 +1552,16 @@ class CalibrateMagnetometerComplete(Frame): state="disabled", pady=5, padx=5) self.copy_calibration_button.grid(row=0, column=1, padx=5, pady=5) - row_counter += 1 + self.import_calibration_data_button = Button(save_calibration_results_frame, text="Import data set", + command=self.import_csv_calibration_raw_data, + state="active", + pady=5, padx=5) + self.import_calibration_data_button.grid(row=0, column=2, padx=5, pady=5) + # Notes on the calibration method calibration_method_notes_frame = LabelFrame(self.right_column, text="Calibration method notes:") calibration_method_notes_frame.grid(row=row_counter, column=1, padx=(100, 0), pady=20, sticky="nw") - label = "-Implementation of calibration according to Kok et al. [ISBN: 978-0-9824438-5-9]\n-Implementation of ellipsoid fit according to Li et al. [DOI: 10.1109/GMAP.2004.1290055]\n-Points created by Fibonacci sphere\n-Accounts for soft-iron and hard-iron effects!" + label = "-Implementation of calibration according to Kok et al. [ISBN: 978-0-9824438-5-9]\n-Implementation of ellipsoid fit according to Li et al. [DOI: 10.1109/GMAP.2004.1290055]\n-Points created by Fibonacci sphere\n-Accounts for soft-iron (A matrix) and hard-iron (b offset vector) effects!\n-MEasured to calibrated field function: h=A^-1 (h_m-b)" calibration_method_notes = Label(calibration_method_notes_frame, anchor='w', justify='left', text=label) calibration_method_notes.grid(row=1, column=0, padx=5, pady=5, sticky="nw") #FLAG @@ -1682,7 +1669,7 @@ class CalibrateMagnetometerComplete(Frame): elif cmd == 'progress': self.calibration_procedure_progress_var.set(min(int(arg * 100), 100)) elif cmd == 'calibration_data': - self.display_calibration_results(arg) + self.display_calibration_results_complete(arg) else: ui_print("Error: Unexpected mpi command '{}' in CalibrationTool".format(cmd)) except queue.Empty: @@ -1697,7 +1684,7 @@ class CalibrateMagnetometerComplete(Frame): def deactivate_buttons(self): self.start_calibration_button.configure(text="Running...", state=DISABLED) - def display_calibration_results(self, results): + def display_calibration_results_complete(self, results): # Cache raw experiment data for saving later self.calibration_raw_results = results['raw_data'] @@ -1705,16 +1692,22 @@ class CalibrateMagnetometerComplete(Frame): results = results['results'] # Display calibration in GUI - for i in range(3): - self.sensitivity_result_vars[i].set("{:.3f}".format(results[i]['sensitivity'])) - self.offset_result_vars[i].set("{:.3f}".format(results[i]['offset'] * 1e6)) - self.angle_to_plane_result_vars[i].set("{:.3f}".format(results[i]['alpha'] * 180 / pi)) - self.angle_in_plane_result_vars[i].set("{:.3f}".format(results[i]['beta'] * 180 / pi)) - self.residual_result_vars[i].set("{:.3e}".format(results[i]['residual'] * 1e6)) + self.mag_amp_avg_set_result_vars.set("{:.3f}".format(results['mag_amp_avg_set'])) + self.total_error_result_vars.set("{:.3f}".format(results['total_error'])) + for row in range(3): + self.b_result_vars[row].set("{:.3f}".format(results[row][column]['b'])) + self.n_result_vars[row].set("{:.3f}".format(results[row][column]['b'])) + for column in range(3): + #FLAG + self.a_mat_result_vars[row][column].set("{:.3f}".format(results[row][column]['a_mat'])) + self.a_mat_inv_result_vars[row][column].set("{:.3f}".format(results[row][column]['a_mat_inv'])) + self.q_mat_result_vars[row][column].set("{:.3f}".format(results[row][column]['q_mat'])) - # Populate clipboard string + # Populate clipboard string #FLAG!!! + self.clipboard = "Not implemented yet" + """ self.clipboard = "\tX\tY\tZ\n" - self.clipboard += "Sensitivity [-]" + self.clipboard += "A_mat [-]" for i in range(3): self.clipboard += "\t{:.3f}".format(results[i]['sensitivity']) self.clipboard += "\nOffset [uT]" @@ -1729,6 +1722,7 @@ class CalibrateMagnetometerComplete(Frame): self.clipboard += "\nResidual [uT]" for i in range(3): self.clipboard += "\t{:.3e}".format(results[i]['residual'] * 1e6) + """ # Enable save buttons self.export_calibration_button.configure(state="normal") @@ -1755,6 +1749,11 @@ class CalibrateMagnetometerComplete(Frame): save_dict_list_to_csv('magnetometer_calibration.csv', self.calibration_raw_results, query_path=True) ui_print("Saved calibration results to magnetometer_calibration.csv.") + def import_csv_calibration_raw_data(self): + self.calibration_raw_results, filename = load_dict_list_from_csv('magnetometer_calibration.csv',query_path=True) + print(self.calibration_raw_results) + ui_print("Loaded calibration results from {}".format(filename)) + def export_csv_cos_trans_matrix(self): cos_trans_matrix = [ {'XX': "{:.5f}".format(self.mgm_to_helmholtz_cos_trans[0][0].get()), diff --git a/src/utility.py b/src/utility.py index d03e536..d29a48b 100644 --- a/src/utility.py +++ b/src/utility.py @@ -1,6 +1,6 @@ import csv from tkinter import filedialog - +import numpy as np import src.globals as g @@ -31,4 +31,17 @@ def save_dict_list_to_csv(filename, data, query_path=False): csv_writer.writeheader() for row in data: - csv_writer.writerow(row) \ No newline at end of file + csv_writer.writerow(row) + + +def load_dict_list_from_csv(filename, query_path=False): + """ Reads a csv file under the specified path containing one row for each dict in the list 'data'. + The file header containing the keys of the first dict entry is deleted upon reading. + Each dict should use the same keys.""" + if query_path: + filename = filedialog.askopenfilename(initialfile=filename, title="Select csv file location...", + filetypes=(("CSV", "*.csv"),)) + + data = np.genfromtxt(filename, dtype=float, delimiter=',') + data = data[1:len(data), :] # remove header + return data, filename