diff --git a/.png b/.png new file mode 100644 index 0000000..8b20a04 Binary files /dev/null and b/.png differ diff --git a/src/calibration.py b/src/calibration.py index bb23905..0f87c08 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -410,12 +410,13 @@ 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): + def __init__(self, view_queue, calibration_points, calibration_interval, 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.matrix_trans_mgm_to_hh = [[x.get() for x in row] for row in mgm_to_helmholtz_cos_trans] + self.right_column = right_column # Hardware checks are done in the init method to allow for exception handling in main thread # This means the run method should/must be called directly after Thread object creation. @@ -754,6 +755,7 @@ class MagnetometerCalibrationComplete(Thread): def plot_magnetometer_calibration(self, 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): plot_fontsize = 5 + ax_width = 0.2 # Plot frame (overwrite plotframe) plot_frame = LabelFrame(self.right_column, text="Result plots:") @@ -773,16 +775,23 @@ class MagnetometerCalibrationComplete(Thread): ax1.set_ylabel(r'$B_y [{\mu}T]$', fontsize=plot_fontsize) ax1.set_zlabel(r'$B_z [{\mu}T]$', fontsize=plot_fontsize) ax1.tick_params(axis='both', labelsize=plot_fontsize) + for axis in ['top', 'bottom', 'left', 'right']: + ax1.spines[axis].set_linewidth(ax_width) + ax1.xaxis.set_tick_params(width=ax_width) + ax1.yaxis.set_tick_params(width=ax_width) + ax1.zaxis.set_tick_params(width=ax_width) + ax1.grid(which='major', linewidth=ax_width) + ax1.grid(which='minor', linewidth=ax_width/2) # linking lines between measured and calibrated points for i, j, k, l, m, n in zip(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, mag_x_m * u_tesla, mag_y_m * u_tesla, mag_z_m * u_tesla): - ax1.plot([i, l], [j, m], [k, n], linewidth=0.5, color='b') + ax1.plot([i, l], [j, m], [k, n], linewidth=0.2, color='b') # set magnetic vectors - l1 = ax1.scatter(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, s=5, color='k', + l1 = ax1.scatter(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, s=1, color='k', label="$B_{set}$ Helmholtz field") # measured values - l2 = ax1.scatter(mag_x_m * u_tesla, mag_y_m * u_tesla, mag_z_m * u_tesla, s=5, color='b', + l2 = ax1.scatter(mag_x_m * u_tesla, mag_y_m * u_tesla, mag_z_m * u_tesla, s=1, color='b', label="$B_{raw}$ uncalibrated") # plot sphere with magnitude u = np.linspace(0, 2 * np.pi, 100) @@ -790,8 +799,8 @@ class MagnetometerCalibrationComplete(Thread): x = np.outer(np.cos(u), np.sin(v)) * mag_amp_avg_set y = np.outer(np.sin(u), np.sin(v)) * mag_amp_avg_set z = np.outer(np.ones(np.size(u)), np.cos(v)) * mag_amp_avg_set - ax1.plot_wireframe(x * u_tesla, y * u_tesla, z * u_tesla, rstride=10, cstride=10, alpha=0.7, color='y') - ax1.plot_surface(x * u_tesla, y * u_tesla, z * u_tesla, alpha=0.3, color='y') + ax1.plot_wireframe(x * u_tesla, y * u_tesla, z * u_tesla, rstride=10, cstride=10, alpha=0.7, color='y', linewidth=0.1) + ax1.plot_surface(x * u_tesla, y * u_tesla, z * u_tesla, alpha=0.3, color='y', linewidth=0.1) # ax1.legend(loc='upper right', fontsize=plot_fontsize) # Calibrated result plot @@ -800,16 +809,23 @@ class MagnetometerCalibrationComplete(Thread): ax2.set_ylabel(r'$B_y [\mu T]$', fontsize=plot_fontsize) ax2.set_zlabel(r'$B_z [\mu T]$', fontsize=plot_fontsize) ax2.tick_params(axis='both', labelsize=plot_fontsize) + for axis in ['top', 'bottom', 'left', 'right']: + ax2.spines[axis].set_linewidth(ax_width) + ax2.xaxis.set_tick_params(width=ax_width) + ax2.yaxis.set_tick_params(width=ax_width) + ax2.zaxis.set_tick_params(width=ax_width) + ax2.grid(which='major', linewidth=ax_width) + ax2.grid(which='minor', linewidth=ax_width/2) # linking lines between measured and calibrated points for i, j, k, l, m, n in zip(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, cal_x * u_tesla, cal_y * u_tesla, cal_z * u_tesla): - ax2.plot([i, l], [j, m], [k, n], linewidth=0.5, color='r') + ax2.plot([i, l], [j, m], [k, n], linewidth=0.2, color='r') # set magnetic vectors - l3 = ax2.scatter(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, s=5, color='k', + l3 = ax2.scatter(mag_x_set * u_tesla, mag_y_set * u_tesla, mag_z_set * u_tesla, s=1, color='k', label="$B_{set}$ Helmholtz field") # calibrated values ellipsoid fit - l4 = ax2.scatter(cal_x * u_tesla, cal_y * u_tesla, cal_z * u_tesla, s=5, color='r', + l4 = ax2.scatter(cal_x * u_tesla, cal_y * u_tesla, cal_z * u_tesla, s=1, color='r', label="$B_{cal,el}$ ellipsoid fit") # plot sphere with magnitude u = np.linspace(0, 2 * np.pi, 100) @@ -817,17 +833,18 @@ class MagnetometerCalibrationComplete(Thread): x = np.outer(np.cos(u), np.sin(v)) * mag_amp_avg_set y = np.outer(np.sin(u), np.sin(v)) * mag_amp_avg_set z = np.outer(np.ones(np.size(u)), np.cos(v)) * mag_amp_avg_set - ax2.plot_wireframe(x * u_tesla, y * u_tesla, z * u_tesla, rstride=10, cstride=10, alpha=0.7, color='y') - ax2.plot_surface(x * u_tesla, y * u_tesla, z * u_tesla, alpha=0.3, color='y') + ax2.plot_wireframe(x * u_tesla, y * u_tesla, z * u_tesla, rstride=10, cstride=10, alpha=0.7, color='y', linewidth=0.1) + ax2.plot_surface(x * u_tesla, y * u_tesla, z * u_tesla, alpha=0.3, color='y', linewidth=0.1) # ax2.legend(loc='upper right', fontsize=plot_fontsize) ax3 = fig1.add_subplot(222) ax3.axis('off') ax3.legend([l1, l2], ["$B_{set}$", "$B_{raw}$"], - loc='upper right', fontsize=plot_fontsize) + loc='right', fontsize=plot_fontsize) ax4 = fig1.add_subplot(224) ax4.axis('off') ax4.legend([l3, l4], ["$B_{set}$", "$B_{cal,el}$"], - loc='upper right', fontsize=plot_fontsize) + loc='right', fontsize=plot_fontsize) + fig1.subplots_adjust(bottom=0.15, left=0.05, right=0.95, top=1.0) canvas1.draw() canvas1.get_tk_widget().grid(row=0, column=0) @@ -836,41 +853,59 @@ class MagnetometerCalibrationComplete(Thread): fig2.clf() # clear figure from previous use canvas2 = FigureCanvasTkAgg(fig2, plot_frame) # x panel - ax3 = fig2.add_subplot(311) - ax3.set_ylabel(r'$B_x [\mu T]$', fontsize=plot_fontsize) - ax3.plot(np.linspace(1, meas_no, meas_no), mag_x_set * u_tesla, linewidth=0.5, color='k', linestyle='solid', + ax5 = fig2.add_subplot(311) + ax5.grid(which='major', linewidth=ax_width) + ax5.grid(which='minor', linewidth=ax_width/2) + ax5.set_ylabel(r'$B_x [\mu T]$', fontsize=plot_fontsize) + ax5.plot(np.linspace(1, meas_no, meas_no), mag_x_set * u_tesla, linewidth=0.2, color='k', linestyle='solid', label=r'$B_{x,set}$') - ax3.plot(np.linspace(1, meas_no, meas_no), mag_x_m * u_tesla, linewidth=0.5, color='b', linestyle='solid', + ax5.plot(np.linspace(1, meas_no, meas_no), mag_x_m * u_tesla, linewidth=0.2, color='b', linestyle='solid', label=r'$B_{x,m}$') - ax3.plot(np.linspace(1, meas_no, meas_no), cal_x * u_tesla, linewidth=0.5, color='r', linestyle='solid', + ax5.plot(np.linspace(1, meas_no, meas_no), cal_x * u_tesla, linewidth=0.2, color='r', linestyle='solid', label=r'$B_{x,el}$') - ax3.legend(loc='upper right', fontsize=plot_fontsize) - ax3.tick_params(axis='both', labelsize=plot_fontsize) - ax3.axes.get_xaxis().set_visible(False) - # y panel - ax4 = fig2.add_subplot(312) - ax4.set_ylabel(r'$B_y [\mu T]$', fontsize=plot_fontsize) - ax4.plot(np.linspace(1, meas_no, meas_no), mag_y_set * u_tesla, linewidth=0.5, color='k', linestyle='solid', - label=r'$B_{y,set}$') - ax4.plot(np.linspace(1, meas_no, meas_no), mag_y_m * u_tesla, linewidth=0.5, color='b', linestyle='solid', - label=r'$B_{y,m}$') - ax4.plot(np.linspace(1, meas_no, meas_no), cal_y * u_tesla, linewidth=0.5, color='r', linestyle='solid', - label=r'$B_{y,el}$') - ax4.legend(loc='upper right', fontsize=plot_fontsize) - ax4.tick_params(axis='both', labelsize=plot_fontsize) - ax4.axes.get_xaxis().set_visible(False) - # z panel - ax5 = fig2.add_subplot(313) - ax5.set_xlabel("Measurement number", fontsize=plot_fontsize) - ax5.set_ylabel(r'$B_z [\mu T]$', fontsize=plot_fontsize) - ax5.plot(np.linspace(1, meas_no, meas_no), mag_z_set * u_tesla, linewidth=0.5, color='k', linestyle='solid', - label=r'$B_{z,set}$') - ax5.plot(np.linspace(1, meas_no, meas_no), mag_z_m * u_tesla, linewidth=0.5, color='b', linestyle='solid', - label=r'$B_{z,m}$') - ax5.plot(np.linspace(1, meas_no, meas_no), cal_z * u_tesla, linewidth=0.5, color='r', linestyle='solid', - label=r'$B_{z,el}$') ax5.legend(loc='upper right', fontsize=plot_fontsize) ax5.tick_params(axis='both', labelsize=plot_fontsize) + ax5.axes.get_xaxis().set_visible(False) + for axis in ['top', 'bottom', 'left', 'right']: + ax5.spines[axis].set_linewidth(ax_width) + ax5.xaxis.set_tick_params(width=ax_width) + ax5.yaxis.set_tick_params(width=ax_width) + # y panel + ax6 = fig2.add_subplot(312) + ax6.grid(which='major', linewidth=ax_width) + ax6.grid(which='minor', linewidth=ax_width/2) + ax6.set_ylabel(r'$B_y [\mu T]$', fontsize=plot_fontsize) + ax6.plot(np.linspace(1, meas_no, meas_no), mag_y_set * u_tesla, linewidth=0.2, color='k', linestyle='solid', + label=r'$B_{y,set}$') + ax6.plot(np.linspace(1, meas_no, meas_no), mag_y_m * u_tesla, linewidth=0.2, color='b', linestyle='solid', + label=r'$B_{y,m}$') + ax6.plot(np.linspace(1, meas_no, meas_no), cal_y * u_tesla, linewidth=0.2, color='r', linestyle='solid', + label=r'$B_{y,el}$') + ax6.legend(loc='upper right', fontsize=plot_fontsize) + ax6.tick_params(axis='both', labelsize=plot_fontsize) + ax6.axes.get_xaxis().set_visible(False) + for axis in ['top', 'bottom', 'left', 'right']: + ax6.spines[axis].set_linewidth(ax_width) + ax6.xaxis.set_tick_params(width=ax_width) + ax6.yaxis.set_tick_params(width=ax_width) + # z panel + ax7 = fig2.add_subplot(313) + ax7.grid(which='major', linewidth=ax_width) + ax7.grid(which='minor', linewidth=ax_width/2) + ax7.set_xlabel("Measurement number", fontsize=plot_fontsize) + ax7.set_ylabel(r'$B_z [\mu T]$', fontsize=plot_fontsize) + ax7.plot(np.linspace(1, meas_no, meas_no), mag_z_set * u_tesla, linewidth=0.2, color='k', linestyle='solid', + label=r'$B_{z,set}$') + ax7.plot(np.linspace(1, meas_no, meas_no), mag_z_m * u_tesla, linewidth=0.2, color='b', linestyle='solid', + label=r'$B_{z,m}$') + ax7.plot(np.linspace(1, meas_no, meas_no), cal_z * u_tesla, linewidth=0.2, color='r', linestyle='solid', + label=r'$B_{z,el}$') + ax7.legend(loc='upper right', fontsize=plot_fontsize) + ax7.tick_params(axis='both', labelsize=plot_fontsize) + for axis in ['top', 'bottom', 'left', 'right']: + ax7.spines[axis].set_linewidth(ax_width) + ax7.xaxis.set_tick_params(width=ax_width) + ax7.yaxis.set_tick_params(width=ax_width) canvas2.draw() canvas2.get_tk_widget().grid(row=0, column=1) diff --git a/src/user_interface.py b/src/user_interface.py index b5cc2c1..f177b75 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -10,6 +10,7 @@ from tkinter import ttk from tkinter import messagebox from tkinter import filedialog from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt # import general packages: import numpy as np @@ -1625,7 +1626,7 @@ class CalibrateMagnetometerComplete(Frame): self.copy_calibration_button.grid(row=0, column=1, padx=5, pady=5) self.import_calibration_data_button = Button(save_calibration_results_frame, text="Import data set", command=self.import_csv_calibration_raw_data, - state="active", + state="normal", pady=5, padx=5) self.import_calibration_data_button.grid(row=0, column=2, padx=5, pady=5) @@ -1633,10 +1634,35 @@ class CalibrateMagnetometerComplete(Frame): # Notes on the calibration method calibration_method_notes_frame = LabelFrame(self.right_column, text="Calibration method notes:") calibration_method_notes_frame.grid(row=0, column=0, 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 (A matrix) and hard-iron (b offset vector) effects!\n-MEasured to calibrated field function: h=A^-1 (h_m-b)" + 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=0, column=0, padx=5, pady=5, sticky="nw") + # Plot frame (overwrite plotframe) + plot_frame = LabelFrame(self.right_column, text="Result plots:") + plot_frame.grid(row=1, column=0, padx=(100, 0), pady=20, sticky="nw") + fig1 = plt.figure('MGM_cal_complete_left', figsize=(2.5, 3), dpi=100) + canvas1 = FigureCanvasTkAgg(fig1, plot_frame) + canvas1.draw() + canvas1.get_tk_widget().grid(row=0, column=0, columnspan=2) + fig2 = plt.figure('MGM_cal_complete_right', figsize=(4, 3), dpi=100) + canvas2 = FigureCanvasTkAgg(fig2, plot_frame) + canvas2.draw() + canvas2.get_tk_widget().grid(row=0, column=2, columnspan=2) + # Export figures with buttons + self.export_figure_left_button = Button(plot_frame, text="Export graph to file", + command=lambda: self.export_figure1_to_file(fig1), + state="active", + pady=5, padx=5) + self.export_figure_left_button.grid(row=1, column=0, padx=5, pady=5) + self.export_figure_right_button = Button(plot_frame, text="Export graph to file", + command=lambda: self.export_figure2_to_file(fig2), + state="normal", + pady=5, padx=5) + self.export_figure_right_button.grid(row=1, column=2, padx=5, pady=5) + + # FLAG Buttons + # This starts an endless polling loop self.update_view() @@ -1744,7 +1770,8 @@ class CalibrateMagnetometerComplete(Frame): self.calibration_thread = MagnetometerCalibrationComplete(self.view_mpi_queue, calibration_points, calibration_interval, - self.mgm_to_helmholtz_cos_trans) + self.mgm_to_helmholtz_cos_trans, + self.right_column) self.calibration_thread.start() self.deactivate_buttons() except (DeviceAccessError, TclError) as e: @@ -1758,26 +1785,30 @@ class CalibrateMagnetometerComplete(Frame): ui_print("Saved calibration results to magnetometer_calibration.csv.") def import_csv_calibration_raw_data(self): - data, raw_data, filename = load_dict_list_from_csv('magnetometer_calibration.csv', query_path=True) - if data is None or filename is None: - ui_print("File could not be loaded! Read file name is {]".format(filename)) - return - ui_print("Loaded calibration results from {}".format(filename)) - ui_print("Size of read data is {}.".format(data.shape)) - if data.shape[1] != 6: - ui_print("File does not have 6 columns instead of {}!".format(data.shape)) - ui_print("[h_x_set|h_y_set|h_z_set|h_x_m|h_y_m|h_z_m|") - return - self.calibration_raw_results = raw_data + try: + data, raw_data, filename = load_dict_list_from_csv('magnetometer_calibration.csv', query_path=True) + if data is None or filename is None: + ui_print("File could not be loaded! Read file name is {]".format(filename)) + return + ui_print("Loaded calibration results from {}".format(filename)) + ui_print("Size of read data is {}.".format(data.shape)) + if data.shape[1] != 6: + ui_print("File does not have 6 columns instead of {}!".format(data.shape)) + ui_print("[h_x_set|h_y_set|h_z_set|h_x_m|h_y_m|h_z_m|") + return + self.calibration_raw_results = raw_data - # 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( - self.view_mpi_queue, raw_data, self.mgm_to_helmholtz_cos_trans) - MagnetometerCalibrationComplete.plot_magnetometer_calibration(self, 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) - # Pass results to UI - self.put_message('calibration_data', {'results': sensor_parameters, 'raw_data': raw_data}) + # 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( + self.view_mpi_queue, raw_data, self.mgm_to_helmholtz_cos_trans) + MagnetometerCalibrationComplete.plot_magnetometer_calibration(self, 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) + # Pass results to UI + self.put_message('calibration_data', {'results': sensor_parameters, 'raw_data': raw_data}) + except TypeError: + ui_print('File path is incorrect, no data could be loaded!') def export_csv_cos_trans_matrix(self): cos_trans_matrix = [ @@ -1794,8 +1825,25 @@ class CalibrateMagnetometerComplete(Frame): if cos_trans_matrix is None: ui_print("Error: Failed to export non-existent coordinate transformation matrix.") return - save_dict_list_to_csv('magnetometer_cos_trans_matrix.csv', cos_trans_matrix, query_path=True) - ui_print("Saved MGM to Helmholtz coordinate transformation matrix to magnetometer_cos_trans_matrix.csv.") + try: + save_dict_list_to_csv('magnetometer_cos_trans_matrix.csv', cos_trans_matrix, query_path=True) + ui_print("Saved MGM to Helmholtz coordinate transformation matrix to magnetometer_cos_trans_matrix.csv.") + except FileNotFoundError: + ui_print('Did not save matrix since incorrect file path specified!') + + def export_figure1_to_file(self, figure): + filename = 'MGM_calibration_ellipsoid_fit_3D' + filename = filedialog.asksaveasfilename(initialfile=filename, title="Select location to save figure.", + filetypes=[("PNG", '*.png'), ("PDF", '*.pdf')], + defaultextension=".pdf") + figure.savefig(filename, dpi=2000, transparent=True) + + def export_figure2_to_file(self, figure): + filename = 'MGM_calibration_ellipsoid_fit_2D' + filename = filedialog.asksaveasfilename(initialfile=filename, title="Select location to save figure.", + filetypes=[("PNG", '*.png'), ("PDF", '*.pdf')], + defaultextension=".pdf") + figure.savefig(filename, dpi=1000, transparent=True) def put_message(self, command, arg): self.view_mpi_queue.put({'cmd': command, 'arg': arg}) diff --git a/src/utility.py b/src/utility.py index 88d707f..9497720 100644 --- a/src/utility.py +++ b/src/utility.py @@ -41,7 +41,6 @@ def load_dict_list_from_csv(filename, query_path=False): 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 # Save data point to raw_data list