diff --git a/src/calibration.py b/src/calibration.py index 6cf0c3c..6430c5c 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -175,7 +175,7 @@ class CoilConstantCalibration(Thread): field = g.MAGNETOMETER.field field_diff_mag = np.linalg.norm(field - ambient_field) sign = 1 if field[i] - ambient_field[i] >= 0 else -1 - k =(field_diff_mag * sign) / c + k = (field_diff_mag * sign) / c k_samples.append(k) # Save vector as principal coil direction if it is the last sample with the largest positive current @@ -272,6 +272,10 @@ class MagnetometerCalibration(Thread): # Zikmund, A. & Janosek, Michal. (2014). Calibration procedure for triaxial magnetometers without a compensating # system or moving parts. + # This contains the raw experiment data for exporting + # Each row is a dict containing the applied vector and measured vector + raw_data = [] + # Find sensor offsets. They must be found prior to applying the chosen calibration algorithm # This will be accurate if the cage was recently calibrated self.cage_dev.set_field_compensated([0, 0, 0]) @@ -279,6 +283,9 @@ class MagnetometerCalibration(Thread): time.sleep(self.calibration_interval) # The offsets can easily be read from the magnetometer offsets = g.MAGNETOMETER.field + # Save data point to raw_data list + raw_data.append({'applied_x': 0, 'applied_y': 0, 'applied_z': 0, + 'measured_x': offsets[0], 'measured_y': offsets[1], 'measured_z': offsets[2]}) # Set new progress indicator for UI self.set_progress(True, 0) @@ -299,12 +306,17 @@ class MagnetometerCalibration(Thread): time.sleep(self.calibration_interval) # Read output and save to array for solver later - reading = g.MAGNETOMETER.field - offsets + raw_reading = g.MAGNETOMETER.field + reading = raw_reading - offsets for i in range(3): row = {'m': reading[i], 'b_x': applied_vec[0], 'b_y': applied_vec[1], 'b_z': applied_vec[2]} # print("[Axis {}] {}".format(i, row)) samples[i].append(row) + # Save data point to raw_data list + raw_data.append({'applied_x': applied_vec[0], 'applied_y': applied_vec[1], 'applied_z': applied_vec[2], + 'measured_x': raw_reading[0], 'measured_y': raw_reading[1], 'measured_z': raw_reading[2]}) + # Set new progress indicator for UI self.set_progress(True, vec_idx + 1) @@ -315,7 +327,7 @@ class MagnetometerCalibration(Thread): sensor_parameters = self.solve_system(samples, offsets) # Pass results to UI - self.put_message('calibration_data', sensor_parameters) + self.put_message('calibration_data', {'results': sensor_parameters, 'raw_data': raw_data}) def set_progress(self, offset_complete, test_vec_index): progress = int(offset_complete) * 0.2 + (test_vec_index / self.calibration_points) * 0.8 diff --git a/src/user_interface.py b/src/user_interface.py index 2ccbb1f..94ed13e 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -686,7 +686,7 @@ class CalibrateAmbientField(Frame): command=self.save_and_apply_ambient_calibration, state="disabled") self.ambient_field_save_results_button.grid(row=0, column=0, padx=5, pady=5) - self.save_ambient_calibration_button = Button(save_ambient_field_results_frame, text="Export to CSV", + self.save_ambient_calibration_button = Button(save_ambient_field_results_frame, text="Export raw CSV", command=self.save_to_csv_ambient_field, state="disabled", pady=5, padx=5) @@ -749,7 +749,7 @@ class CalibrateAmbientField(Frame): command=self.save_and_apply_coil_constants, state="disabled") self.coil_constant_save_results_button.grid(row=0, column=0, padx=5, pady=5) - self.save_k_calibration_button = Button(save_coil_constant_results_frame, text="Export to CSV", + self.save_k_calibration_button = Button(save_coil_constant_results_frame, text="Export raw CSV", command=self.save_to_csv_coil_constants, state="disabled", pady=5, padx=5) @@ -978,6 +978,8 @@ class CalibrateMagnetometer(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 # UI Elements row_counter = 0 @@ -1107,6 +1109,20 @@ class CalibrateMagnetometer(Frame): 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") + # Save calibration buttons + save_calibration_results_frame = Frame(calibration_results_frame) + save_calibration_results_frame.grid(row=6, 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, + state="disabled", + pady=5, padx=5) + self.export_calibration_button.grid(row=0, column=0, padx=5, pady=5) + self.copy_calibration_button = Button(save_calibration_results_frame, text="Copy to clipboard", + command=self.copy_to_clipboard, + state="disabled", + pady=5, padx=5) + self.copy_calibration_button.grid(row=0, column=1, padx=5, pady=5) row_counter += 1 # This starts an endless polling loop @@ -1161,6 +1177,13 @@ class CalibrateMagnetometer(Frame): self.start_calibration_button.configure(text="Running...", state=DISABLED) def display_calibration_results(self, results): + # Cache raw experiment data for saving later + self.calibration_raw_results = results['raw_data'] + + # Unpack the dict + 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)) @@ -1168,6 +1191,28 @@ class CalibrateMagnetometer(Frame): 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)) + # Populate clipboard string + self.clipboard = "\tX\tY\tZ\n" + self.clipboard += "Sensitivity [-]" + for i in range(3): + self.clipboard += "\t{:.3f}".format(results[i]['sensitivity']) + self.clipboard += "\nOffset [uT]" + for i in range(3): + self.clipboard += "\t{:.3f}".format(results[i]['offset'] * 1e6) + self.clipboard += "\nAngle to XY Plane [deg]" + for i in range(3): + self.clipboard += "\t{:.3f}".format(results[i]['alpha'] * 180/pi) + self.clipboard += "\nAngle in XY Plane [deg]" + for i in range(3): + self.clipboard += "\t{:.3f}".format(results[i]['beta'] * 180 / pi) + 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") + self.copy_calibration_button.configure(state="normal") + def start_calibration_procedure(self): try: calibration_points = self.calibration_points_var.get() @@ -1180,6 +1225,19 @@ class CalibrateMagnetometer(Frame): except (DeviceAccessError, TclError) as e: messagebox.showwarning("Calibration failed", "Failed to start calibration:\n{}".format(e)) + def export_csv(self): + if self.calibration_raw_results is None: + ui_print("Error: Failed to export non-existent calibration data.") + return + + 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 copy_to_clipboard(self): + self.clipboard_clear() + self.clipboard_append(self.clipboard) + self.update() + class HardwareConfiguration(Frame): """Settings window to set program constants"""