66 Commits

Author SHA1 Message Date
mkoller 767f6e39f8 Plot formatting improved 2023-03-03 10:54:16 +01:00
mkoller b3edafa796 Plotting only part of csv if csv has more than 1000 plot points 2023-03-03 10:33:07 +01:00
mkoller 9184dd1271 Update csv_threading.py 2023-03-02 16:35:47 +01:00
mkoller 42b9590159 Remove numpy requirement to stop dependency conflict 2023-03-02 11:59:42 +01:00
mkoller a49f98ba63 Updating canvas slows down loop. only increment it if new field is set
Takes ca 50-100ms
2023-02-24 11:45:33 +01:00
mkoller c0fb152efa Updating matplotlib version likely solves tkinter plot_canvas.draw() threading issue 2023-02-24 11:40:25 +01:00
mkoller ae71a8d31b Tkinter plot_canvas.draw() Crash
Tkinter might have issues with thread safety:
https://github.com/matplotlib/matplotlib/issues/13293
2023-02-24 11:18:15 +01:00
mkoller c138facd59 Minor formatting warnings for tkinter gui fixed 2023-02-23 18:30:08 +01:00
mkoller 70fa24f905 Introduced red status indicator line, minor warning fixes 2023-02-23 18:17:01 +01:00
mkoller d0649a1af5 Added compensate field checkbox, minor adaptation 2023-02-23 15:17:20 +01:00
mkoller c8b3a5e0ba Reset csv generation inputs to last reasonable value 2023-02-22 17:14:45 +01:00
mkoller ac475e1b81 Includes rotation rates to plots 2023-02-20 18:27:14 +01:00
mkoller 5662a73345 Introduced adaptable rotation rates 2023-02-20 16:51:30 +01:00
mkoller 6194035099 Introduced checkboxes to control ambient field compensation (untested!) 2023-02-13 09:44:42 +01:00
mkoller 5f161fe09e Delete development file for rotation field 2023-02-11 21:56:46 +01:00
mkoller aa9efdba5a Minor whitespace adaptations 2023-02-11 21:56:17 +01:00
mkoller 0683979b3b Minor bug fixes 2023-02-11 21:55:26 +01:00
mkoller c5a9c2649b Replacing csv sequence decimal and seperator signs throuout code and examples
decimal sign now point, seperator now comma
2023-02-11 21:30:36 +01:00
mkoller 25513cc7c5 Rotation rate csv file generator initial issue added 2023-02-11 21:14:34 +01:00
mkoller 9eb95b56bd Create rotating_field.py
Dummyscript for csv execution sequence -  will be updated and integrated to GUI
2023-02-10 16:42:02 +01:00
mkoller 35a0779b2d Implemented oversampling (untested!) 2023-01-31 10:54:09 +01:00
mkoller e9937b1269 Oversampling x5 2023-01-30 14:31:40 +01:00
mkoller c110605ce7 Added set magnetic field and vector norms to the field data displays
Marius fancy implementation using globals
2023-01-30 14:16:49 +01:00
mkoller 7f6244f337 BUG: Calibration routine uses raw field instead of compensated field
Substracting the ambient field from the set magnetic field values during calibration, no longer substract initial offset measurement from all readings
2023-01-30 14:15:43 +01:00
mkoller 85cad05b6e Gitignore 2023-01-30 07:55:20 +01:00
mkoller 960ae27f6b Added units to Magnetometer Results frame 2023-01-29 13:40:34 +01:00
mkoller 6568515957 Implemented slightly different calibration routine from nliaudat
https://github.com/nliaudat/magnetometer_calibration
2023-01-29 13:33:28 +01:00
mkoller a6e1b43d49 Added units to calibration frames 2023-01-29 12:32:34 +01:00
mkoller 982a7d7a20 Introduce global to limit max field 2023-01-29 12:16:37 +01:00
mkoller 03eaf8330c Set custom magnetic field magnitude during calibration 2023-01-29 12:09:43 +01:00
mkoller 1354aed150 Opening console on largest screen in zoomed mode 2023-01-29 11:34:19 +01:00
lteichroeb 56b8ffcd5e Use GUI transformation matrix on imported datasets 2023-01-26 19:01:53 +01:00
lteichroeb 298852f6c8 Renamed reinitialize buttons 2023-01-26 14:01:45 +01:00
muellerr 82b0d1cfd5 Merge branch 'master' of https://egit.irs.uni-stuttgart.de/eive/Helmholtz_Test_Bench 2023-01-26 13:17:47 +01:00
muellerr c678332b45 fixed loading CSV trafo matrix 2023-01-26 11:54:00 +01:00
lteichroeb f13389c06d Added Reinitialize buttons to all frames 2023-01-26 11:43:23 +01:00
lteichroeb 3262d30b9a Possible mistake with getter of tkinter/numpy
Error was get() not possible for float values
2023-01-25 17:27:55 +01:00
lteichroeb e107d15cf5 Fixed FGM3D mapping matrix 2023-01-24 18:28:29 +01:00
lteichroeb c638f5a278 Versioning conflict with numpy/pandas 2023-01-24 11:37:44 +01:00
lteichroeb 827af90bdc Minor documentation fixes 2023-01-24 11:36:51 +01:00
lteichroeb dfd0fd8ecc Fixed daylight saving bug in fgm3d_adapter script 2023-01-24 11:36:01 +01:00
lteichroeb 842739b4b3 Minor changes to documentation 2022-10-20 18:12:40 +02:00
lteichroeb 00a1669f39 Deleted misplaced file 2022-10-14 11:04:28 +02:00
mkoller 3b4bbc91da Reduces font size if screen is too small to display GUI properly. 2022-10-13 18:05:46 +02:00
mkoller cd79c993f2 Fixed copy calibration results to clipboard 2022-10-12 11:38:36 +02:00
mkoller 380c8c9664 Label change 2022-10-11 19:09:40 +02:00
mkoller c704886717 Slightly increased size of of main frame if window not maximised 2022-10-09 11:58:27 +02:00
mkoller c5f8bb9414 Fixed function argument warnings 2022-10-08 18:47:00 +02:00
mkoller 3fe39b0cf9 Fixed minor warnings and style issues 2022-10-08 18:38:24 +02:00
mkoller d5c9879eae Fixed graph export buttons 2022-10-08 18:12:18 +02:00
mkoller effcc72966 Cleaned up minor issues 2022-10-07 16:01:00 +02:00
mkoller 283ef5fd24 Merged Calibration files, fixed transformation, GUI display 2022-10-07 15:00:25 +02:00
mkoller 4021aa5383 Read in results from csv 2022-10-06 09:13:26 +02:00
mkoller 95984329c1 Added calibration model for full calibration 2022-10-06 09:13:15 +02:00
mkoller 2bce7ffd85 Dublicates calibration method, added notes 2022-10-03 15:08:08 +02:00
lteichroeb 998b325ca9 Some fixes 2022-08-17 15:28:33 +02:00
mkoller 9e046e25ab Gram-Schmidt Orthonomalisation, fixed COS matrix input/output 2022-08-16 23:53:09 +02:00
lteichroeb 5ad5e5ebff Update 2022-08_2Do_Helmholtz_tmp.txt 2022-08-16 16:51:13 +02:00
lteichroeb b626ef3ae8 Updates to normalisation matrix 2022-08-16 16:51:00 +02:00
lteichroeb 88f85607be Added transformation matrix input to gui 2022-08-16 15:27:06 +02:00
lteichroeb 6b1a27d9ae Commented Documentation about Magnetometer activation 2022-08-02 15:40:06 +02:00
lteichroeb 157f3dc9e3 Changed name of JDK Compiler 2022-08-02 14:08:35 +02:00
lteichroeb 1703e99684 Added magnetometer documentation. 2021-11-11 09:47:30 +01:00
lteichroeb 1ccd1e8c63 Added more documentation 2021-11-11 09:04:28 +01:00
lteichroeb 53d198a064 Fixed inaccuracies in rest of documentation. 2021-11-10 23:44:30 +01:00
lteichroeb 952200c730 Removed auto-py-to-exe. Not working. 2021-11-10 23:14:43 +01:00
27 changed files with 2545 additions and 235 deletions
+2 -1
View File
@@ -103,5 +103,6 @@ ENV/
config.ini
log.csv
.idea/misc.xml
.idea/misc.xml
.idea/Python-PS2000B.iml
*.xml
*.iml
+3 -1
View File
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.9 (Helmholtz_Test_Bench)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
+2 -1
View File
@@ -173,4 +173,5 @@
\newacronym{pla}{PLA}{Polylactic Acid, a thermoplastic polyester}
\newacronym{pc}{PC}{Personal Computer}
\newacronym{tbd}{TBD}{To Be Determined}
\newacronym{mpi}{MPI}{Message Passing Interface}
\newacronym{mpi}{MPI}{Message Passing Interface}
\newacronym{dut}{DUT}{Device Under Test}
+4 -3
View File
@@ -165,7 +165,7 @@ More details on the execution thread is provided in Section \ref{sec:csv_exec}.
\myparagraph{Magnetometer Calibration Mode Class \code{CalibrateMagnetometer}}
This class constructs magnetometer calibration interface.
It is placed in the application's main area, its layout is shown in Figure TODO.
It is placed in the application's main area, its layout is shown in Figure \ref{fig:magcalibrationpure}.
This class creates and manages a separate execution thread defined in \code{calibration.py} to preform the calibration without blocking the \gls{ui}.
Hardware control is acquired upon creation of the thread, or an exception is returned otherwise.
More details on the calibration thread is provided in Section \ref{sec:calibration_processes}.
@@ -236,7 +236,7 @@ The line plot is modified to visually reflect the discrete and nearly instantane
\subsection{Calibration Procedures \code{calibration.py}}
\label{sec:calibration_processes}
This file contains the worker thread objects \code{AmbientFieldCalibration}, \code{CoilConstantCalibration}, and \code{MagnetometerCalibration}.
This file contains the worker thread objects \code{AmbientFieldCalibration}, \code{CoilConstantCalibration}, \code{MagnetometerCalibrationSimple}, and \code{MagnetometerCalibrationComplete}.
All of these threads start by checking for and acquiring hardware during instantiation, while still in the main thread.
Then upon running, the main code in \code{calibration\_procedure} is executed; This function may freely block and make use of sleep commands.
Typically, calibration procedures should save two data detail levels: Processed data that is displayed as a final result, and detailed, raw data points that can be exported by the user to apply custom algorithms or verify the applications function.
@@ -337,7 +337,8 @@ The magnetometer object mirrors the state of the TCP connection, which is presen
\section{Conversion to Executable}
The program is compiled to a .exe executable file to enable simple use without a Python installation. For this, the "Auto Py to Exe" tool developed by B. Vollebregt \cite{auto_py_to_exe} is used. The resulting files are distributed in a separate repository on the \gls{irs} git server.\footnote{\url{https://egit.irs.uni-stuttgart.de/zietzm/Helmholtz_Test_Bench_Releases}} When a new software version is ready for publication, the following procedure should be used to release it:
\begin{enumerate}
\item Run "auto-py-to-exe.exe" (provided in main development repository)
\item \code{pip install auto-py-to-exe} in the project's virtual environment.
\item Execute "auto-py-to-exe" in a console. To make sure the right version is executed, don't install auto-py-to-exe in your system environment.
\item In the Auto Py to Exe \gls{ui}, select \code{main.py} file as "Script Location"
\item Select options "One File" and "Window Based"
\item Select file "Helmholtz.ico" as the "Icon"
+176 -15
View File
@@ -118,8 +118,8 @@ These instructions assume a disassembly according to Section \ref{sec:disassembl
\section{Software Users Guide}\label{sec:software_guide}
\subsection{Installation}
\begin{enumerate}
\item Download latest release: \url{https://egit.irs.uni-stuttgart.de/zietzm/Helmholtz_Test_Bench_Releases/releases}
\item Unpack ZIP-folder and run "Helmholtz Cage Control.exe"
\item Download latest release: \url{https://egit.irs.uni-stuttgart.de/eive/Helmholtz_Test_Bench/releases}
\item Unpack ZIP-folder and run "Release{\textbackslash}Helmholtz Control.exe"
\item Setup hardware and program according to Section \ref{sec:software_init}
\end{enumerate}
@@ -232,7 +232,7 @@ The manual input mode is used to set static currents or magnetic fields on the t
\item For magnetic fields, choose whether ambient field should be compensated by (un)ticking the checkbox
\item Press "Execute" button, devices will now implement set values
\item Check console output to see if any errors occurred
\item Monitor behaviour in status display and on devices
\item Monitor behavior in status display and on devices
\item When finished, press "Power Down All" button to remove currents from the test bench
\end{enumerate}
@@ -240,9 +240,9 @@ The manual input mode is used to set static currents or magnetic fields on the t
This mode is used to run timed sequences of magnetic fields. These have to be defined in a \gls{csv} file of the following form:
\begin{itemize}
\item \textit{Column separator:} Semicolon (;)
\item \textit{Decimal:} Comma (,)
\item \textit{Decimal:} Comma (,) or Period (.)
\item \textit{Line terminator:} Tested with Windows standard (\code{\textbackslash r\textbackslash n}), other options may work as well
\item \textit{Columns:} Time in seconds; X-axis, Y-axis and Z-axis flux density in Tesla
\item \textit{Columns:} Time in seconds; X-axis, Y-axis and Z-axis flux density in Tesla
\end{itemize}
An example for the \gls{csv} file structure is given below:
%[caption=Example \gls{csv} file]
@@ -284,11 +284,124 @@ The \gls{ui} layout is shown in Figure \ref{fig:csvmodepure}, its main elements
\item Monitor execution in console, status display and on devices
\end{enumerate}
\subsubsection*{Ambient Field and Coil Constant Calibration}
\label{sec:ambient_field_calibration}
The application offers calibration tools to determine the exact ambient field with the intention of cancelling it, as well for measuring the test bench's coil constants.
These are both integrated into one application view, depicted in Figure \ref{fig:ambientcalibrationpure}.
All the calibration tools require access to the complete Helmholtz cage hardware, as well as a magnetometer.
These must be connected before starting the test.
It is important that the magnetometer is centered as well as possible to achieve accurate results.
For these tests, where the magnetometer itself is not the \gls{dut}, it is recommended to use the IRS's FGM3D reference magnetometer.
Further, an adapter script (\code{fgm3d\_adapter.py}) already exists for this sensor.
For more details on writing adapter scripts and connecting magnetometers, please refer to Section \ref{sec:tcp_api}.
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{media/ambient_calibration_pure}
\caption{Ambient field and coil constant calibration view.}
\label{fig:ambientcalibrationpure}
\end{figure}
After setting up the magnetometer, operation is simple: Click on the buttons to ``Calibrate Ambient Field'' or ``Calibrate Coil Constants''.
This will cause the calibration procedure to start.
The current status will be shown in the progress bar underneath.
After completing, the results of the calibration procedure will be saved into the data fields on the right hand side of Figure \ref{fig:ambientcalibrationpure}.
At this point, the buttons underneath the respective will become available.
These two calibrations are primarily used to determine important application config parameters, namely the ambient field and coil constant as their names imply.
To automatically apply the newly measured values, click ``Save and apply''.
The ``Copy to clipboard'' will put the results table as shown in the \gls{ui} into the system clipboard, from which it can be pasted into software such as Microsoft Excel and LibreOffice Calc.
If semi-raw experiment data is required to verify the program functioning or to apply custom algorithms, it can be exported with the ``Export raw CSV'' button.
\vspace{5mm}
\textbf{Ambient Field Calibration Method}
This calibration uses a P-controller (implemented as PI-controller with $I=0$) to attempt to reach zero as a set point for the magnetometer.
Updates are preformed approximately every half second by default and the calibration executes for 45 seconds.
The procedure consistently achieves zero offsets below \SI{10}{\nano\tesla}.
The ambient field exported by this calibration has been processed by multiplying the current that was required with the current coil constants, thus it is strongly recommended to first preform the coil constant calibration if both are to be executed.
\vspace{5mm}
\textbf{Coil Constant Calibration Method}
The coil consant calibration uses a linear distribution of currents in each axis individually to collect coil consant data.
Each setpoint is held for 3 seconds before being measured, and by default 8 points distributed across \SI{-3}{\ampere} to \SI{3}{\ampere}.
To calculate the individual setpoints corresponding to each setpoint, first the field magnitude compared to the inital conditions is calculated, second the sign is estimated and reapplied, third the field is divided by the applied current.
The final result is the average of all constants.
In addition to the primary coil constant measurement, the maximum setpoints are also sampled once for each axis to calculate the angles between the coils.
This is simply done by calculating the angle between the measured vectors.
To understand the calibration methods in all detail, it is recommended to look at the \code{calibration.py} source file.
\subsubsection*{Magnetometer Calibration}
\label{sec:magnetometer_calibration}
The helmholtz control software supports calibrating magnetometers.
In preparation, a calibration of the ambient field using the reference magnetometer (FGM3D) should be conducted beforehand to achieve accurate results.
Afterwards, the magnetometer must be replaced with the \gls{dut}.
Since the software only supports one magnetometer at once, the reference magnetometer (meaning: its adapter script) should be disabled beforehand.
Tip: To mount CubeSat magnetometers a purpose built PC104 holder can be found in accompanyment of the Helmholtz cage.
As the coordinate system of the Helmholtz Cage and the magnetometer may not match, there are two options that available.
First, the calibration can be conducted as-is, and the calibration results will reflect the unexpected coordinate system by often yielding negative sensitivities and untypical sensor axis angles.
These values are not invalid, but describe a sensor correction that would transform it into the Helmholtz coordinate system.
With some work, the desired calibration parameters could be extracted mathematically by changing signs and adding or subtracting \SI{90}{\degree} increments.
The second option would be to change the sensors orientation in software in the adapter script that is used.
This yields a more expected set of calibration parameters, but these now do not describe the initial axes anymore.
In this case, the parameters must be correlated back to their initial axes and angles must be negated if the axis was flipped.
This common issue should be solved robustly in the control/calibration software in the future.
\begin{figure}[h]
\centering
\includegraphics[width=\linewidth]{media/magnetometer_calibration_pure}
\caption{Magnetometer calibration view.}
\label{fig:magcalibrationpure}
\end{figure}
The procedure to start the magnetometer can be done as follows:
\begin{itemize}
\item Activate the FGMM3D box, start the \textit{FGM3D TD Application} software and check if the device was recognissed automatically (Port: FGM3D TD (000125).
If the device was not recognized, click on \textit{File -> Rescan} devices, else restart the computer.
\item Click on \textit{connct} and \textit{start}. Telemetry should now be displayed in the application. Use the \textit{Live Streaming} console and stream to \textbf{COM10}
\item Start the \textit{Helmholtz Control.exe} software and navigate to e.g. \textit{Menu -> Ambient Filed Calibration}
Start the \textit{fgm3dcadapter.py} script that forwards the data live stream from \textit{COM10} on the Sensys app to \textbf{COM11} of the Helmholtz application.
%\item Start the data live stream in the FGM3D software by first starting the data acquisition and then live streaming function. Check if the console of the applocation says "Magnetometer State: active"
\item The test bench should be ready to run e.g. the Magnetometer Calibrtation script.
\end{itemize}
The calibration method used (Zikmund \cite{ref:calibration_procedure_magnetometer_helmholtz_cage}) relies on a simplified error model and the Helmholtz test bench.
After either measuring the local geomagnetic field or cancelling it, the magnetometer-under-test runs through a sequence of magnetic fields generated by the Helmholtz coils, which supplies sufficient data to solve a system of equations containing the coefficients of interest.
The non-linear system is constructed with the equation below on a per-axis basis, with one row for every sample.
\begin{equation*}
B_{meas} = S \left( B_E \sin{\alpha_E} + B_x \cos{\alpha} \cos{\beta} + B_y \cos{\alpha} \sin{\beta} + B_z \sin{\alpha} \right)
\end{equation*}
The calibration procedure makes use of nearly equidistantly distributed vectors in all directions as test points.
The number of these points, as well as the settle time before taking a measurement, can be set in the \gls{ui}.
Generally, a high number of points, such as larger than 8, is recommended to achieve both an accurate result and also to get useful residual data.
The meaning of the individual calibration coefficients can be derived from Zikmund \cite{ref:calibration_procedure_magnetometer_helmholtz_cage} or also the thesis accompanying the Helmholtz test bench \cite{ref:leons_test_bench}.
After completing, the results of the calibration procedure will be saved into the data fields on the right hand side of Figure \ref{fig:magcalibrationpure}.
This also enables the save buttons underneath.
The ``Copy to clipboard'' will put the results table as shown in the \gls{ui} into the system clipboard, from which it can be pasted into software such as Microsoft Excel and LibreOffice Calc.
If semi-raw experiment data is required to verify the program functioning or to apply custom algorithms, it can be exported with the ``Export raw CSV'' button.
\subsubsection*{Data Logging Configuration Page}\label{sec:logging_guide}
The application has the ability to log test bench data to a \gls{csv} file. The data is temporarily stored internally and must be saved to an external file by user request.\\
An example of a log file is given in Appendix \ref{app:example_files}. The first three columns are time stamps: date, system time and time since the start of logging in seconds. The other columns contain the data, as selected by the user. All dynamic values from the status display can be logged, see Table \ref{tab:status_contents} for explanations. Each type of data is logged for all three axes. The units for numerical values are Volt, Ampere and Tesla.\\
There are two options as for when and how often data is logged. The first option logs a row of data in a regular time interval specified by the user. The second option logs, whenever a significant command is sent to the test bench, for example when a new field vector is commanded. Both options can be used simultaneously.\\
The logging configuration \gls{ui} is shown in Figure \ref{fig:loggingpure}. Its elements are listed below.
The application has the ability to log test bench data to a \gls{csv} file.
The data is temporarily stored internally and must be saved to an external file by user request.
The logging output is highly customizable using the options shown in Figure \ref{fig:loggingpure}.
An example of a log file is given in Appendix \ref{app:example_files}.
Unless explicitly disabled, the first three columns are time stamps: date, system time and time since the start of logging in seconds.
All dynamic values from the status display as well as the magnetometer status can be logged, see Table \ref{tab:status_contents} for explanations.
Each type of data is logged for all three axes by default, but this can also be specified.
The units for numerical values are Volt, Ampere and Tesla.
There are two options as for when and how often data is logged.
The first option logs a row of data in a regular time interval specified by the user.
The second option logs, whenever a significant command is sent to the test bench, for example when a new field vector is commanded.
Both options can be used simultaneously.
The logging configuration \gls{ui} is shown in Figure \ref{fig:loggingpure}.
Its elements are listed below.
\begin{figure}[h]
\centering
@@ -309,7 +422,8 @@ The logging configuration \gls{ui} is shown in Figure \ref{fig:loggingpure}. Its
\item \textbf{"Datapoints logged" counter:} Displays the current number of logged data rows
\item \textbf{"Log in regular intervals" controls:} Enable checkbox to periodically log data, set interval (in seconds) in the entry field to the right
\item \textbf{"Log whenever test bench is commanded" checkbox:} Enable, to log data on significant changes to the test bench (e.g. a new field vector is commanded)
\item \textbf{Data selection checkboxes:} Select what data to log, explanations are given in Table \ref{tab:status_contents}
\item \textbf{Data selection checkboxes:} Select what data to log, explanations for most options are given in Table \ref{tab:status_contents}.
In addition, the Log X/Y/Z-Axis Data checkboxes specify whether the selected logging variables will be included for the respective axis.
\end{itemize}
\textbf{To collect and save log data:}
\begin{enumerate}
@@ -351,11 +465,10 @@ Figure \ref{fig:settingspure} shows a screenshot of the \gls{ui} layout with the
\item Opens dialogue to let user choose new file path and name (must be *.ini)
\item Reinitializes test bench devices with current settings
\end{itemize}
\item \textbf{"\gls{psu} Serial Port" entries:} Input \gls{com} ports for both \gls{psu}s here
\item \textbf{"Serial Port" entries:} Input \gls{com} ports for both \gls{psu}s and the switch box here
\begin{itemize}
\item Use Windows device manager to find correct port names (connect \gls{psu}s separately to differentiate between devices)
\item Use Windows device manager to find correct port names (connect devices separately to differentiate between devices)
\item Test bench X- and Y-axes need to be connected to channel 1 and 2 of one \gls{psu}, Z-axis to channel 1 of the other
\item \textit{Note: Switch box Arduino should be found automatically}
\end{itemize}
\item \textbf{Program constant entry fields:} Set constants here, details are listed in Table \ref{tab:settings_entries}
\item \textbf{"Update and Reinitialize" button:} Implements any changed settings in the program and on test bench devices, needs to be pressed for changes to take effect
@@ -419,7 +532,7 @@ Figure \ref{fig:settingspure} shows a screenshot of the \gls{ui} layout with the
\item If program has not been configured before:
\begin{enumerate}
\setcounter{enumii}{2}
\item Use Windows device manager to find correct serial \gls{com} ports for \gls{psu}s (connect /disconnect in turn to differentiate between devices)
\item Use Windows device manager to find correct serial \gls{com} ports for \gls{psu}s and Arduino (connect /disconnect in turn to differentiate between devices)
\item Enter \gls{com} port names in application (switch box should be found automatically)
\item Press "Update and Reinitialize" button
\end{enumerate}
@@ -435,6 +548,7 @@ Figure \ref{fig:settingspure} shows a screenshot of the \gls{ui} layout with the
\setcounter{enumii}{5}
\item Check console print to see if all devices were found, otherwise check physical connections and \gls{com} port settings
\end{enumerate}
\item If using a magnetometer, the listening port is open and the adapter script can now be started
\item Test configuration
\begin{enumerate}
\item Go to manual mode (Menu$\rightarrow$Static Manual Input)
@@ -444,6 +558,7 @@ Figure \ref{fig:settingspure} shows a screenshot of the \gls{ui} layout with the
\item Current should be activated on correct \gls{psu} channel
\item For negative currents, corresponding status \gls{led} on switch box should light up and relay actuation be audible as clicking sound
\end{itemize}
\item If using a magnetometer: Check the device output in either of the calibration-views
\end{enumerate}
\item Go back to the settings page (Menu$\rightarrow$Settings...)
\item Change program constants as needed (e.g. enter measured ambient field), see Section \ref{sec:settings_guide}
@@ -457,4 +572,50 @@ Figure \ref{fig:settingspure} shows a screenshot of the \gls{ui} layout with the
\subsection{TCP Remote Control}
\label{sec:tcp_api}
TODO.
The Helmholtz Control Software offers a TCP automation interface that is opened upon application start-up.
To find out which port is being listened on, look for the corresponding information in the application console, but typically, it will be port 6677.
The commands that are accepted by the TCP interface are documented in Table \ref{tab:tcp_comamnds} and also in the \code{socket\_control.py} source file.
All field commands will implicitly preform the usual safety checks to ensure safe operation.
The commands that are shown must all be terminated with a single {\textbackslash}n (newline) char.
Commands may be split across multiple packets if desired.
Important Note: Before useful commands can be sent, \code{declare\_api\_version} must be called.
The \code{tools} folder in the application git repository contains an example implementation (\code{fgm3d\_adapter.py}) of a magnetometer interface using the TCP socket.
%\small
\begin{longtable}{lp{8.5cm}}
\caption{TCP Remote Control Commands}\\
\hline
\textbf{Command} & \textbf{Description}\\ \hline
\code{set\_raw\_field} [X] [Y] [Z] & Returns: 0 or 1 for success.
Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it.
The field units are Tesla.
This causes an additional field of the given strength to be generated, without regard for the pre-existing geomagnetic/external fields. \\ \hline
\code{set\_compensated\_field} [X] [Y] [Z] & Returns: 0 or 1 for success.
Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it.
The field units are Tesla.
This causes a field of exactly the given magnitude to be generated by compensating external factors such as the
geomagnetic field. \\ \hline
\code{set\_coil\_currents} [X] [Y] [Z] & Returns: 0 or 1 for success.
Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it.
The field units are Ampere.
This establishes the requested current in the individual coils. \\ \hline
\code{magnetometer\_field} [X] [Y] [Z] & Returns: 1.
Accepts decimal point formatted floats, with or without scientific notation. The float() cast must understand it.
The field units are Tesla.
Sets the state of a virtual magnetometer object which mirrors a physical sensor by means of
this command. \\ \hline
\code{get\_api\_version} & Returns: a string uniquely identifying each API version.
This function can be called before \code{declare\_api\_version}. \\ \hline
\code{declare\_api\_version} [version] & Returns: 0 or 1.
Declare the API version the client application was programmed for.
It must be compatible with the current API version. This prevents unexpected behavior by forcing programmers to specify which API they are expecting.
This function must be called before sending HW commands. \\ \hline
\label{tab:tcp_comamnds}
\end{longtable}
%\normalsize
+1
View File
@@ -14,6 +14,7 @@
\begin{center}
Author:\\
Martin Zietz\\
Leon Teichröb\\
Supervisors:\\
M.Sc. Markus T. Koller\\
+19
View File
@@ -172,4 +172,23 @@
author={Stier, Annika and Schweigert, Robin and Galla, Daniel and Lengowski, Michael and Klinkner, Sabine},
year={2020},
publisher={University of Leicester}
}
@inproceedings{ref:calibration_procedure_magnetometer_helmholtz_cage,
author = {Zikmund, A. and Janosek, Michal},
year = {2014},
month = {05},
pages = {473-476},
title = {Calibration procedure for triaxial magnetometers without a compensating system or moving parts},
isbn = {9781467363860},
journal = {Conference Record - IEEE Instrumentation and Measurement Technology Conference},
doi = {10.1109/I2MTC.2014.6860790}
}
@thesis{ref:leons_test_bench,
author = {Leon Teichröb},
title = {Mapping and Calibration of a Helmholtz Magnetic Field Cage and Test of the EIVE Attitude Control System},
date = {2021},
institution = {Institute for Space Systems (IRS), University of Stuttgart},
location = {Stuttgart},
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.
+23 -23
View File
@@ -1,23 +1,23 @@
Time (s);xField (T);yField (T);zField (T)
0,5;0,000015;0,000025;0,00002
1;0,0000155;0,0000245;0,0000205
1,5;0,000016;0,000024;0,000021
2;0,0000165;0,0000235;0,0000215
2,5;0,000017;0,000023;0,000022
3;0,0000175;0,0000225;0,0000225
3,5;0,000018;0,000022;0,000023
4;0,0000185;0,0000215;0,0000235
4,5;0,000019;0,000021;0,000024
5;0,0000195;0,0000205;0,0000245
5,5;0,00002;0,00002;0,000025
6;0,0000205;0,0000195;0,0000245
6,5;0,000021;0,000019;0,000024
7;0,0000215;0,0000185;0,0000235
7,5;0,000022;0,000018;0,000023
8;0,0000225;0,0000175;0,0000225
8,5;0,000023;0,000017;0,000022
9;0,0000235;0,0000165;0,0000215
9,5;0,000024;0,000016;0,000021
10;0,0000245;0,0000155;0,0000205
10,5;0,000025;0,000015;0,00002
11;0,000025;0,000015;0,00002
Time [s]],xField [T],yField [T],zField [T],Rotation Rate [deg/s]
0.5,0.000015,0.000025,0.00002,1
1,0.0000155,0.0000245,0.0000205,1
1.5,0.000016,0.000024,0.000021,1
2,0.0000165,0.0000235,0.0000215,1
2.5,0.000017,0.000023,0.000022,1
3,0.0000175,0.0000225,0.0000225,1
3.5,0.000018,0.000022,0.000023,1
4,0.0000185,0.0000215,0.0000235,1
4.5,0.000019,0.000021,0.000024,1
5,0.0000195,0.0000205,0.0000245,1
5.5,0.00002,0.00002,0.000025,1
6,0.0000205,0.0000195,0.0000245,1
6.5,0.000021,0.000019,0.000024,1
7,0.0000215,0.0000185,0.0000235,1
7.5,0.000022,0.000018,0.000023,1
8,0.0000225,0.0000175,0.0000225,1
8.5,0.000023,0.000017,0.000022,1
9,0.0000235,0.0000165,0.0000215,1
9.5,0.000024,0.000016,0.000021,1
10,0.0000245,0.0000155,0.0000205,1
10.5,0.000025,0.000015,0.00002,1
11,0.000025,0.000015,0.00002,1
1 Time (s) Time [s]] xField (T) xField [T] yField (T) yField [T] zField (T) zField [T] Rotation Rate [deg/s]
2 0,5 0.5 0,000015 0.000015 0,000025 0.000025 0,00002 0.00002 1
3 1 1 0,0000155 0.0000155 0,0000245 0.0000245 0,0000205 0.0000205 1
4 1,5 1.5 0,000016 0.000016 0,000024 0.000024 0,000021 0.000021 1
5 2 2 0,0000165 0.0000165 0,0000235 0.0000235 0,0000215 0.0000215 1
6 2,5 2.5 0,000017 0.000017 0,000023 0.000023 0,000022 0.000022 1
7 3 3 0,0000175 0.0000175 0,0000225 0.0000225 0,0000225 0.0000225 1
8 3,5 3.5 0,000018 0.000018 0,000022 0.000022 0,000023 0.000023 1
9 4 4 0,0000185 0.0000185 0,0000215 0.0000215 0,0000235 0.0000235 1
10 4,5 4.5 0,000019 0.000019 0,000021 0.000021 0,000024 0.000024 1
11 5 5 0,0000195 0.0000195 0,0000205 0.0000205 0,0000245 0.0000245 1
12 5,5 5.5 0,00002 0.00002 0,00002 0.00002 0,000025 0.000025 1
13 6 6 0,0000205 0.0000205 0,0000195 0.0000195 0,0000245 0.0000245 1
14 6,5 6.5 0,000021 0.000021 0,000019 0.000019 0,000024 0.000024 1
15 7 7 0,0000215 0.0000215 0,0000185 0.0000185 0,0000235 0.0000235 1
16 7,5 7.5 0,000022 0.000022 0,000018 0.000018 0,000023 0.000023 1
17 8 8 0,0000225 0.0000225 0,0000175 0.0000175 0,0000225 0.0000225 1
18 8,5 8.5 0,000023 0.000023 0,000017 0.000017 0,000022 0.000022 1
19 9 9 0,0000235 0.0000235 0,0000165 0.0000165 0,0000215 0.0000215 1
20 9,5 9.5 0,000024 0.000024 0,000016 0.000016 0,000021 0.000021 1
21 10 10 0,0000245 0.0000245 0,0000155 0.0000155 0,0000205 0.0000205 1
22 10,5 10.5 0,000025 0.000025 0,000015 0.000015 0,00002 0.00002 1
23 11 11 0,000025 0.000025 0,000015 0.000015 0,00002 0.00002 1
+15 -15
View File
@@ -1,15 +1,15 @@
Time (s);xField (T);yField (T);zField (T)
0;0,0000145;0,0000255;0,0000195
0,1;-0,000015;0,000025;0,00002
0,2;-0,0000155;-0,0000245;0,0000205
0,3;-0,000016;-0,000024;-0,000021
0,4;0,0000165;-0,0000235;-0,0000215
0,5;0,000017;0,000023;-0,000022
0,6;0,0000175;0,0000225;0,0000225
0,7;-0,000018;-0,000022;0,000023
0,8;0,0000185;-0,0000215;-0,0000235
0,9;-0,000019;0,000021;-0,000024
1;-0,0000195;-0,0000205;-0,0000245
1,1;0,00002;0,00002;0,000025
1,2;-0,0000205;-0,0000195;-0,0000245
1,3;0,000021;0,000019;0,000024
Time [s]],xField [T],yField [T],zField [T],Rotation Rate [deg/s]
0,0.0000145,0.0000255,0.0000195,1
0.1,-0.000015,0.000025,0.00002,1
0.2,-0.0000155,-0.0000245,0.0000205,1
0.3,-0.000016,-0.000024,-0.000021,1
0.4,0.0000165,-0.0000235,-0.0000215,1
0.5,0.000017,0.000023,-0.000022,1
0.6,0.0000175,0.0000225,0.0000225,1
0.7,-0.000018,-0.000022,0.000023,1
0.8,0.0000185,-0.0000215,-0.0000235,1
0.9,-0.000019,0.000021,-0.000024,1
1,-0.0000195,-0.0000205,-0.0000245,1
1.1,0.00002,0.00002,0.000025,1
1.2,-0.0000205,-0.0000195,-0.0000245,1
1.3,0.000021,0.000019,0.000024,1
1 Time (s) Time [s]] xField (T) xField [T] yField (T) yField [T] zField (T) zField [T] Rotation Rate [deg/s]
2 0 0 0,0000145 0.0000145 0,0000255 0.0000255 0,0000195 0.0000195 1
3 0,1 0.1 -0,000015 -0.000015 0,000025 0.000025 0,00002 0.00002 1
4 0,2 0.2 -0,0000155 -0.0000155 -0,0000245 -0.0000245 0,0000205 0.0000205 1
5 0,3 0.3 -0,000016 -0.000016 -0,000024 -0.000024 -0,000021 -0.000021 1
6 0,4 0.4 0,0000165 0.0000165 -0,0000235 -0.0000235 -0,0000215 -0.0000215 1
7 0,5 0.5 0,000017 0.000017 0,000023 0.000023 -0,000022 -0.000022 1
8 0,6 0.6 0,0000175 0.0000175 0,0000225 0.0000225 0,0000225 0.0000225 1
9 0,7 0.7 -0,000018 -0.000018 -0,000022 -0.000022 0,000023 0.000023 1
10 0,8 0.8 0,0000185 0.0000185 -0,0000215 -0.0000215 -0,0000235 -0.0000235 1
11 0,9 0.9 -0,000019 -0.000019 0,000021 0.000021 -0,000024 -0.000024 1
12 1 1 -0,0000195 -0.0000195 -0,0000205 -0.0000205 -0,0000245 -0.0000245 1
13 1,1 1.1 0,00002 0.00002 0,00002 0.00002 0,000025 0.000025 1
14 1,2 1.2 -0,0000205 -0.0000205 -0,0000195 -0.0000195 -0,0000245 -0.0000245 1
15 1,3 1.3 0,000021 0.000021 0,000019 0.000019 0,000024 0.000024 1
+15 -15
View File
@@ -1,15 +1,15 @@
Time (s);xField (T);yField (T);zField (T)
0;0,0000145;0,0000255;0,0000195
1;-0,000015;0,000025;0,00002
2;-0,0000155;-0,0000245;0,0000205
3;-0,000016;-0,000024;-0,000021
4;0,0000165;-0,0000235;-0,0000215
5;0,000017;0,000023;-0,000022
6;0,0000175;0,0000225;0,0000225
7;-0,000018;-0,000022;0,000023
8;0,0000185;-0,0000215;-0,0000235
9;-0,000019;0,000021;-0,000024
10;-0,0000195;-0,0000205;-0,0000245
11;0,00002;0,00002;0,000025
12;-0,0000205;-0,0000195;-0,0000245
13;0,000021;0,000019;0,000024
Time [s]],xField [T],yField [T],zField [T],Rotation Rate [deg/s]
0,0.0000145,0.0000255,0.0000195,1
1,-0.000015,0.000025,0.00002,1
2,-0.0000155,-0.0000245,0.0000205,1
3,-0.000016,-0.000024,-0.000021,1
4,0.0000165,-0.0000235,-0.0000215,1
5,0.000017,0.000023,-0.000022,1
6,0.0000175,0.0000225,0.0000225,1
7,-0.000018,-0.000022,0.000023,1
8,0.0000185,-0.0000215,-0.0000235,1
9,-0.000019,0.000021,-0.000024,1
10,-0.0000195,-0.0000205,-0.0000245,1
11,0.00002,0.00002,0.000025,1
12,-0.0000205,-0.0000195,-0.0000245,1
13,0.000021,0.000019,0.000024,1
1 Time (s) Time [s]] xField (T) xField [T] yField (T) yField [T] zField (T) zField [T] Rotation Rate [deg/s]
2 0 0,0000145 0.0000145 0,0000255 0.0000255 0,0000195 0.0000195 1
3 1 -0,000015 -0.000015 0,000025 0.000025 0,00002 0.00002 1
4 2 -0,0000155 -0.0000155 -0,0000245 -0.0000245 0,0000205 0.0000205 1
5 3 -0,000016 -0.000016 -0,000024 -0.000024 -0,000021 -0.000021 1
6 4 0,0000165 0.0000165 -0,0000235 -0.0000235 -0,0000215 -0.0000215 1
7 5 0,000017 0.000017 0,000023 0.000023 -0,000022 -0.000022 1
8 6 0,0000175 0.0000175 0,0000225 0.0000225 0,0000225 0.0000225 1
9 7 -0,000018 -0.000018 -0,000022 -0.000022 0,000023 0.000023 1
10 8 0,0000185 0.0000185 -0,0000215 -0.0000215 -0,0000235 -0.0000235 1
11 9 -0,000019 -0.000019 0,000021 0.000021 -0,000024 -0.000024 1
12 10 -0,0000195 -0.0000195 -0,0000205 -0.0000205 -0,0000245 -0.0000245 1
13 11 0,00002 0.00002 0,00002 0.00002 0,000025 0.000025 1
14 12 -0,0000205 -0.0000205 -0,0000195 -0.0000195 -0,0000245 -0.0000245 1
15 13 0,000021 0.000021 0,000019 0.000019 0,000024 0.000024 1
+12 -12
View File
@@ -1,12 +1,12 @@
Time (s);xField (T);yField (T);zField (T);
0;0,00015;-0,00015;0,00002;150
1;0,00017;-0,00017;0,00002;170
2;0,00018;-0,00018;0,00002;180
3;0,00019;-0,00019;0,00002;190
4;0,0002;-0,0002;0,00002;200
5;0,00021;0,00021;0,00002;210
6;0,00022;-0,00022;0,00002;220
7;0,0002;-0,0002;0,00002;200
8;0,00018;-0,00018;0,00002;180
9;0,00005;-0,00005;0,00002;50
10;-0,00004;0,00004;0,00002;-40
Time [s]],xField [T],yField [T],zField [T],Rotation Rate [deg/s]
0,0.00015,-0.00015,0.00002,1
1,0.00017,-0.00017,0.00002,1
2,0.00018,-0.00018,0.00002,1
3,0.00019,-0.00019,0.00002,1
4,0.0002,-0.0002,0.00002,1
5,0.00021,0.00021,0.00002,1
6,0.00022,-0.00022,0.00002,1
7,0.0002,-0.0002,0.00002,1
8,0.00018,-0.00018,0.00002,1
9,0.00005,-0.00005,0.00002,1
10,-0.00004,0.00004,0.00002,1
1 Time (s) Time [s]] xField (T) xField [T] yField (T) yField [T] zField (T) zField [T] Rotation Rate [deg/s]
2 0 0,00015 0.00015 -0,00015 -0.00015 0,00002 0.00002 150 1
3 1 0,00017 0.00017 -0,00017 -0.00017 0,00002 0.00002 170 1
4 2 0,00018 0.00018 -0,00018 -0.00018 0,00002 0.00002 180 1
5 3 0,00019 0.00019 -0,00019 -0.00019 0,00002 0.00002 190 1
6 4 0,0002 0.0002 -0,0002 -0.0002 0,00002 0.00002 200 1
7 5 0,00021 0.00021 0,00021 0.00021 0,00002 0.00002 210 1
8 6 0,00022 0.00022 -0,00022 -0.00022 0,00002 0.00002 220 1
9 7 0,0002 0.0002 -0,0002 -0.0002 0,00002 0.00002 200 1
10 8 0,00018 0.00018 -0,00018 -0.00018 0,00002 0.00002 180 1
11 9 0,00005 0.00005 -0,00005 -0.00005 0,00002 0.00002 50 1
12 10 -0,00004 -0.00004 0,00004 0.00004 0,00002 0.00002 -40 1
+181
View File
@@ -0,0 +1,181 @@
"Time [s]]","xField [T]","yField [T]","zField [T]","Rotation Rate [deg/s]"
0.0,0.0,-1.7452406437283511e-06,9.998476951563913e-05,1.0
1.0,0.0,-3.4883798303794917e-06,9.993913750958131e-05,0.9991
2.0,0.0,-5.22923827596647e-06,9.986318173908324e-05,0.9984
3.0,0.0,-6.967638394438434e-06,9.97569647453467e-05,0.9979
4.0,0.0,-8.703403395519831e-06,9.962053387397024e-05,0.9976
5.0,0.0,-1.0436356385678047e-05,9.945392131731718e-05,0.9974999999999999
6.0,0.0,-1.2166319471804114e-05,9.925714417869374e-05,0.9976
7.0,0.0,-1.3893112867193057e-05,9.903020455830349e-05,0.9979
8.0,0.0,-1.561655399941571e-05,9.877308966101715e-05,0.9984
9.0,0.0,-1.733645661968247e-05,9.848577192606996e-05,0.9991
10.0,0.0,-1.9052629913311656e-05,9.816820917887003e-05,1.0
11.0,0.0,-2.076487761093051e-05,9.782034480517372e-05,1.0011
12.0,0.0,-2.247299710005592e-05,9.744210794795481e-05,1.0024
13.0,0.0,-2.4176778536724306e-05,9.703341372736604e-05,1.0039
14.0,0.0,-2.5876003956866184e-05,9.659416348426153e-05,1.0056
15.0,0.0,-2.757044638715028e-05,9.612424504781959e-05,1.0075
16.0,0.0,-2.9259868955055523e-05,9.562353302787435e-05,1.0096
17.0,0.0,-3.094402399796594e-05,9.50918891326348e-05,1.0119
18.0,0.0,-3.262265217112453e-05,9.452916251253802e-05,1.0144
19.0,0.0,-3.429548155432671e-05,9.39351901310517e-05,1.0171
20.0,0.0,-3.596222675728274e-05,9.330979716330852e-05,1.02
21.0,0.0,-3.762258802363148e-05,9.265279742352143e-05,1.0231
22.0,0.0,-3.927625033364511e-05,9.196399382219565e-05,1.0264
23.0,0.0,-4.092288250572579e-05,9.12431788542166e-05,1.0299
24.0,0.0,-4.256213629686185e-05,9.049013511895843e-05,1.0336
25.0,0.0,-4.419364550228159e-05,8.970463587361951e-05,1.0375
26.0,0.0,-4.581702505461894e-05,8.888644562105304e-05,1.0416
27.0,0.0,-4.743187012298561e-05,8.803532073342054e-05,1.0459
28.0,0.0,-4.903775521243089e-05,8.715101011305436e-05,1.0504
29.0,0.0,-5.063423326436127e-05,8.623325589197157e-05,1.0551
30.0,0.0,-5.222083475858915e-05,8.528179417153538e-05,1.06
31.0,0.0,-5.379706681778277e-05,8.429635580381285e-05,1.0651
32.0,0.0,-5.536241231519777e-05,8.327666721622591e-05,1.0704
33.0,0.0,-5.691632898668582e-05,8.222245128113941e-05,1.0759
34.0,0.0,-5.84582485480968e-05,8.113342823207298e-05,1.0816
35.0,0.0,-5.9987575819318076e-05,8.000931662826251e-05,1.0875
36.0,0.0,-6.150368785632916e-05,7.884983436933288e-05,1.0936
37.0,0.0,-6.300593309279031e-05,7.765469976187423e-05,1.0998999999999999
38.0,0.0,-6.449363049283228e-05,7.642363263974053e-05,1.1064
39.0,0.0,-6.596606871686862e-05,7.515635553991005e-05,1.1131
40.0,0.0,-6.74225053024151e-05,7.385259493576245e-05,1.1199999999999999
41.0,0.0,-6.886216586206999e-05,7.251208252963613e-05,1.1271
42.0,0.0,-7.028424330098631e-05,7.113455660653098e-05,1.1343999999999999
43.0,0.0,-7.168789705635229e-05,6.971976345081669e-05,1.1419
44.0,0.0,-7.307225236158857e-05,6.826745882779226e-05,1.1496
45.0,0.0,-7.443639953817088e-05,6.677740953192026e-05,1.1575
46.0,0.0,-7.57793933181956e-05,6.524939500352636e-05,1.1656
47.0,0.0,-7.71002522010209e-05,6.368320901571287e-05,1.1739
48.0,0.0,-7.839795784753942e-05,6.207866143318036e-05,1.1824
49.0,0.0,-7.967145451587092e-05,6.043558004458558e-05,1.1911
50.0,0.0,-8.091964854250044e-05,5.875381246998535e-05,1.2
51.0,0.0,-8.214140787313431e-05,5.703322814482264e-05,1.2090999999999998
52.0,0.0,-8.333556164779959e-05,5.5273720381803435e-05,1.2184
53.0,0.0,-8.45008998449718e-05,5.347520851188933e-05,1.2279
54.0,0.0,-8.563617298978239e-05,5.163764010548956e-05,1.2376
55.0,0.0,-8.674009193162976e-05,4.976099327477715e-05,1.2475
56.0,0.0,-8.781132769679547e-05,4.784527905787572e-05,1.2576
57.0,0.0,-8.884851142194952e-05,4.589054388546408e-05,1.2679
58.0,0.0,-8.985023437471637e-05,4.38968721301249e-05,1.2784
59.0,0.0,-9.081504806776283e-05,4.186438873852005e-05,1.2891
60.0,0.0,-9.174146447316287e-05,3.979326194620631e-05,1.3
61.0,0.0,-9.262795634408807e-05,3.7683706074611496e-05,1.3111000000000002
62.0,0.0,-9.347295765116815e-05,3.5535984409369075e-05,1.3224
63.0,0.0,-9.427486414115998e-05,3.335041215885984e-05,1.3339
64.0,0.0,-9.503203402585546e-05,3.112735949142892e-05,1.3456
65.0,0.0,-9.574278880944718e-05,2.8867254649335716e-05,1.3575000000000002
66.0,0.0,-9.640541426285387e-05,2.6570587137049264e-05,1.3696
67.0,0.0,-9.701816155378307e-05,2.42379109810241e-05,1.3819000000000001
68.0,0.0,-9.75792485415751e-05,2.1869848057577132e-05,1.3944
69.0,0.0,-9.808686124612794e-05,1.946709148493532e-05,1.4071
70.0,0.0,-9.85391555004425e-05,1.7030409074934477e-05,1.42
71.0,0.0,-9.893425879655386e-05,1.4560646839220595e-05,1.4331
72.0,0.0,-9.927027233481838e-05,1.2058732544135474e-05,1.4464
73.0,0.0,-9.954527328671002e-05,9.525679307756304e-06,1.4599000000000002
74.0,0.0,-9.975731728143635e-05,6.962589231804475e-06,1.4736
75.0,0.0,-9.990444112681327e-05,4.370657060339902e-06,1.4875
76.0,0.0,-9.998466577493252e-05,1.7511738563136905e-06,1.5016
77.0,0.0,-9.999599954321475e-05,-8.944693138367792e-07,1.5159
78.0,0.0,-9.993644160145834e-05,-3.5647776983010223e-06,1.5304000000000002
79.0,0.0,-9.980398573546578e-05,-6.258149192448348e-06,1.5451000000000001
80.0,0.0,-9.95966243977505e-05,-8.972870698578612e-06,1.56
81.0,0.0,-9.931235305569338e-05,-1.1707114526701726e-05,1.5751
82.0,0.0,-9.894917484732254e-05,-1.4458934851294935e-05,1.5904
83.0,0.0,-9.850510555462879e-05,-1.722626424014905e-05,1.6059
84.0,0.0,-9.797817890399535e-05,-2.000691027262028e-05,1.6216
85.0,0.0,-9.736645220290827e-05,-2.279855226585184e-05,1.6375000000000002
86.0,0.0,-9.666801232161705e-05,-2.5598738128815886e-05,1.6536
87.0,0.0,-9.58809820278266e-05,-2.8404881365349675e-05,1.6699000000000002
88.0,0.0,-9.500352668181514e-05,-3.121425824871764e-05,1.6864000000000001
89.0,0.0,-9.403386129858073e-05,-3.4024005191618475e-05,1.7031
90.0,0.0,-9.297025798271502e-05,-3.683111633696989e-05,1.7200000000000002
91.0,0.0,-9.181105374067828e-05,-3.963244139623856e-05,1.7371
92.0,0.0,-9.055465867399797e-05,-4.242468376353232e-05,1.7544
93.0,0.0,-8.919956455562706e-05,-4.5204398935131514e-05,1.7719
94.0,0.0,-8.774435379026785e-05,-4.796799326559646e-05,1.7896
95.0,0.0,-8.618770875788803e-05,-5.071172309304299e-05,1.8075
96.0,0.0,-8.452842153791733e-05,-5.3431694267617555e-05,1.8256000000000001
97.0,0.0,-8.276540400970972e-05,-5.6123862118616924e-05,1.8439
98.0,0.0,-8.08976983227803e-05,-5.878403189707582e-05,1.8624
99.0,0.0,-7.892448772806883e-05,-6.140785973197496e-05,1.8811
100.0,0.0,-7.684510775903972e-05,-6.399085413949068e-05,1.9
101.0,0.0,-7.46590577487918e-05,-6.652837812589881e-05,1.9191
102.0,0.0,-7.236601266651652e-05,-6.901565192584626e-05,1.9384000000000001
103.0,0.0,-6.996583525360449e-05,-7.144775641869711e-05,1.9579
104.0,0.0,-6.745858843645298e-05,-7.381963726652448e-05,1.9776000000000002
105.0,0.0,-6.484454798956871e-05,-7.612610981804154e-05,1.9975
106.0,0.0,-6.212421541888612e-05,-7.836186482331717e-05,2.0176000000000003
107.0,0.0,-5.929833103133426e-05,-8.052147500448747e-05,2.0379
108.0,0.0,-5.6367887152580006e-05,-8.259940252782707e-05,2.0584000000000002
109.0,0.0,-5.333414145055902e-05,-8.459000742245954e-05,2.0791000000000004
110.0,0.0,-5.019863031787675e-05,-8.648755699063967e-05,2.1
111.0,0.0,-4.696318226142989e-05,-8.828623625390153e-05,2.1211
112.0,0.0,-4.36299312426667e-05,-8.998015947841043e-05,2.1424
113.0,0.0,-4.0201329906786734e-05,-9.156338282154988e-05,2.1639
114.0,0.0,-3.6680162633885195e-05,-9.302991814008938e-05,2.1856
115.0,0.0,-3.3069558339591776e-05,-9.437374799818187e-05,2.2075
116.0,0.0,-2.9373002947155928e-05,-9.558884191089653e-05,2.2296
117.0,0.0,-2.559435144721021e-05,-9.666917385597483e-05,2.2519
118.0,0.0,-2.1737839455627395e-05,-9.760874108296534e-05,2.2744
119.0,0.0,-1.7808094174002536e-05,-9.84015842448172e-05,2.2971000000000004
120.0,0.0,-1.3810144651370471e-05,-9.904180887235564e-05,2.3200000000000003
121.0,0.0,-9.749431239851467e-06,-9.952360820679386e-05,2.3431
122.0,0.0,-5.631814131040329e-06,-9.984128739951931e-05,2.3664
123.0,0.0,-1.4635808541675665e-06,-9.998928908179772e-05,2.3899
124.0,0.0,2.7485473885872958e-06,-9.996222029973469e-05,2.4136
125.0,0.0,6.997410679702419e-06,-9.975488080179127e-05,2.4375
126.0,0.0,1.1275406772107576e-05,-9.936229265734316e-05,2.4616000000000002
127.0,0.0,1.5574489043303514e-05,-9.87797311751759e-05,2.4859
128.0,0.0,1.98861661624192e-05,-9.800275708040388e-05,2.5104
129.0,0.0,2.4201503618818632e-05,-9.702724989706916e-05,2.5351
130.0,0.0,2.851112726430912e-05,-9.584944247160945e-05,2.56
131.0,0.0,3.2805229023794614e-05,-9.446595655947376e-05,2.5851
132.0,0.0,3.707357493126433e-05,-9.287383938341263e-05,2.6104000000000003
133.0,0.0,4.1305515649210165e-05,-9.107060105738215e-05,2.6359000000000004
134.0,0.0,4.548999962981594e-05,-8.905425275459531e-05,2.6616
135.0,0.0,4.9615589075450825e-05,-8.682334548205341e-05,2.6875
136.0,0.0,5.3670478854007505e-05,-8.43770093069287e-05,2.7136
137.0,0.0,5.764251852133727e-05,-8.171499286249302e-05,2.7399
138.0,0.0,6.151923759832183e-05,-7.883770294295253e-05,2.7664
139.0,0.0,6.528787424386095e-05,-7.574624397762439e-05,2.7931
140.0,0.0,6.893540745711695e-05,-7.24424571554642e-05,2.8200000000000003
141.0,0.0,7.244859293261136e-05,-6.8928958951117e-05,2.8471
142.0,0.0,7.58140026800881e-05,-6.52091787835393e-05,2.8744
143.0,0.0,7.901806850731089e-05,-6.128739551795232e-05,2.9019000000000004
144.0,0.0,8.204712944802844e-05,-5.716877250158922e-05,2.9295999999999998
145.0,0.0,8.488748319909571e-05,-5.2859390803557724e-05,2.9575
146.0,0.0,8.752544161007193e-05,-4.836628030934244e-05,2.9856000000000003
147.0,0.0,8.994739024541854e-05,-4.369744830122675e-05,3.0139000000000005
148.0,0.0,9.213985201359944e-05,-3.886190513744783e-05,3.0423999999999998
149.0,0.0,9.408955482885929e-05,-3.386968662546322e-05,3.0711
150.0,0.0,9.578350324015606e-05,-2.873187266857161e-05,3.1
151.0,0.0,9.720905392760702e-05,-2.3460601750585723e-05,3.1291
152.0,0.0,9.835399492984207e-05,-1.8069080810616793e-05,3.1584
153.0,0.0,9.920662842584688e-05,-1.2571590049629674e-05,3.1879
154.0,0.0,9.975585685224551e-05,-6.9834822026192765e-06,3.2176
155.0,0.0,9.999127209157403e-05,-1.3211758054129605e-06,3.2475000000000005
156.0,0.0,9.990324741902882e-05,4.397858016388089e-06,3.2776
157.0,0.0,9.948303184456377e-05,1.0155115706554476e-05,3.3079
158.0,0.0,9.87228464342335e-05,1.5931088849251602e-05,3.3384
159.0,0.0,9.761598213955617e-05,2.170529960470135e-05,3.3691000000000004
160.0,0.0,9.615689860666532e-05,2.7456344446183743e-05,3.4
161.0,0.0,9.434132337846043e-05,3.31619466135623e-05,3.4311000000000003
162.0,0.0,9.216635084322934e-05,3.879901767110548e-05,3.4624
163.0,0.0,8.963054022274018e-05,4.434372852365659e-05,3.4939
164.0,0.0,8.67340118320907e-05,4.9771590204761896e-05,3.5256
165.0,0.0,8.347854078322675e-05,5.5057544702821115e-05,3.5575
166.0,0.0,7.986764724463343e-05,6.017606603631379e-05,3.5896000000000003
167.0,0.0,7.590668231196969e-05,6.510127172636267e-05,3.6219
168.0,0.0,7.160290848913815e-05,6.980704474403788e-05,3.6544
169.0,0.0,6.696557372729566e-05,7.426716593067309e-05,3.6871
170.0,0.0,6.200597792154295e-05,7.845545680188934e-05,3.72
171.0,0.0,5.673753072246099e-05,8.234593254993105e-05,3.7531
172.0,0.0,5.117579948333757e-05,8.591296495431425e-05,3.7864
173.0,0.0,4.533854613496372e-05,8.913145479777476e-05,3.8199
174.0,0.0,3.924575175943646e-05,9.197701326329688e-05,3.8536
175.0,0.0,3.2919627623696125e-05,9.442615165894024e-05,3.8875
176.0,0.0,2.6384611433804805e-05,9.645647868073527e-05,3.9216000000000006
177.0,0.0,1.9667347583515437e-05,9.804690428070224e-05,3.9559
178.0,0.0,1.2796650196782336e-05,9.917784905784754e-05,3.9904
179.0,0.0,5.803447804817374e-06,9.98314579357467e-05,4.0251
1 Time [s]] xField [T] yField [T] zField [T] Rotation Rate [deg/s]
2 0.0 0.0 -1.7452406437283511e-06 9.998476951563913e-05 1.0
3 1.0 0.0 -3.4883798303794917e-06 9.993913750958131e-05 0.9991
4 2.0 0.0 -5.22923827596647e-06 9.986318173908324e-05 0.9984
5 3.0 0.0 -6.967638394438434e-06 9.97569647453467e-05 0.9979
6 4.0 0.0 -8.703403395519831e-06 9.962053387397024e-05 0.9976
7 5.0 0.0 -1.0436356385678047e-05 9.945392131731718e-05 0.9974999999999999
8 6.0 0.0 -1.2166319471804114e-05 9.925714417869374e-05 0.9976
9 7.0 0.0 -1.3893112867193057e-05 9.903020455830349e-05 0.9979
10 8.0 0.0 -1.561655399941571e-05 9.877308966101715e-05 0.9984
11 9.0 0.0 -1.733645661968247e-05 9.848577192606996e-05 0.9991
12 10.0 0.0 -1.9052629913311656e-05 9.816820917887003e-05 1.0
13 11.0 0.0 -2.076487761093051e-05 9.782034480517372e-05 1.0011
14 12.0 0.0 -2.247299710005592e-05 9.744210794795481e-05 1.0024
15 13.0 0.0 -2.4176778536724306e-05 9.703341372736604e-05 1.0039
16 14.0 0.0 -2.5876003956866184e-05 9.659416348426153e-05 1.0056
17 15.0 0.0 -2.757044638715028e-05 9.612424504781959e-05 1.0075
18 16.0 0.0 -2.9259868955055523e-05 9.562353302787435e-05 1.0096
19 17.0 0.0 -3.094402399796594e-05 9.50918891326348e-05 1.0119
20 18.0 0.0 -3.262265217112453e-05 9.452916251253802e-05 1.0144
21 19.0 0.0 -3.429548155432671e-05 9.39351901310517e-05 1.0171
22 20.0 0.0 -3.596222675728274e-05 9.330979716330852e-05 1.02
23 21.0 0.0 -3.762258802363148e-05 9.265279742352143e-05 1.0231
24 22.0 0.0 -3.927625033364511e-05 9.196399382219565e-05 1.0264
25 23.0 0.0 -4.092288250572579e-05 9.12431788542166e-05 1.0299
26 24.0 0.0 -4.256213629686185e-05 9.049013511895843e-05 1.0336
27 25.0 0.0 -4.419364550228159e-05 8.970463587361951e-05 1.0375
28 26.0 0.0 -4.581702505461894e-05 8.888644562105304e-05 1.0416
29 27.0 0.0 -4.743187012298561e-05 8.803532073342054e-05 1.0459
30 28.0 0.0 -4.903775521243089e-05 8.715101011305436e-05 1.0504
31 29.0 0.0 -5.063423326436127e-05 8.623325589197157e-05 1.0551
32 30.0 0.0 -5.222083475858915e-05 8.528179417153538e-05 1.06
33 31.0 0.0 -5.379706681778277e-05 8.429635580381285e-05 1.0651
34 32.0 0.0 -5.536241231519777e-05 8.327666721622591e-05 1.0704
35 33.0 0.0 -5.691632898668582e-05 8.222245128113941e-05 1.0759
36 34.0 0.0 -5.84582485480968e-05 8.113342823207298e-05 1.0816
37 35.0 0.0 -5.9987575819318076e-05 8.000931662826251e-05 1.0875
38 36.0 0.0 -6.150368785632916e-05 7.884983436933288e-05 1.0936
39 37.0 0.0 -6.300593309279031e-05 7.765469976187423e-05 1.0998999999999999
40 38.0 0.0 -6.449363049283228e-05 7.642363263974053e-05 1.1064
41 39.0 0.0 -6.596606871686862e-05 7.515635553991005e-05 1.1131
42 40.0 0.0 -6.74225053024151e-05 7.385259493576245e-05 1.1199999999999999
43 41.0 0.0 -6.886216586206999e-05 7.251208252963613e-05 1.1271
44 42.0 0.0 -7.028424330098631e-05 7.113455660653098e-05 1.1343999999999999
45 43.0 0.0 -7.168789705635229e-05 6.971976345081669e-05 1.1419
46 44.0 0.0 -7.307225236158857e-05 6.826745882779226e-05 1.1496
47 45.0 0.0 -7.443639953817088e-05 6.677740953192026e-05 1.1575
48 46.0 0.0 -7.57793933181956e-05 6.524939500352636e-05 1.1656
49 47.0 0.0 -7.71002522010209e-05 6.368320901571287e-05 1.1739
50 48.0 0.0 -7.839795784753942e-05 6.207866143318036e-05 1.1824
51 49.0 0.0 -7.967145451587092e-05 6.043558004458558e-05 1.1911
52 50.0 0.0 -8.091964854250044e-05 5.875381246998535e-05 1.2
53 51.0 0.0 -8.214140787313431e-05 5.703322814482264e-05 1.2090999999999998
54 52.0 0.0 -8.333556164779959e-05 5.5273720381803435e-05 1.2184
55 53.0 0.0 -8.45008998449718e-05 5.347520851188933e-05 1.2279
56 54.0 0.0 -8.563617298978239e-05 5.163764010548956e-05 1.2376
57 55.0 0.0 -8.674009193162976e-05 4.976099327477715e-05 1.2475
58 56.0 0.0 -8.781132769679547e-05 4.784527905787572e-05 1.2576
59 57.0 0.0 -8.884851142194952e-05 4.589054388546408e-05 1.2679
60 58.0 0.0 -8.985023437471637e-05 4.38968721301249e-05 1.2784
61 59.0 0.0 -9.081504806776283e-05 4.186438873852005e-05 1.2891
62 60.0 0.0 -9.174146447316287e-05 3.979326194620631e-05 1.3
63 61.0 0.0 -9.262795634408807e-05 3.7683706074611496e-05 1.3111000000000002
64 62.0 0.0 -9.347295765116815e-05 3.5535984409369075e-05 1.3224
65 63.0 0.0 -9.427486414115998e-05 3.335041215885984e-05 1.3339
66 64.0 0.0 -9.503203402585546e-05 3.112735949142892e-05 1.3456
67 65.0 0.0 -9.574278880944718e-05 2.8867254649335716e-05 1.3575000000000002
68 66.0 0.0 -9.640541426285387e-05 2.6570587137049264e-05 1.3696
69 67.0 0.0 -9.701816155378307e-05 2.42379109810241e-05 1.3819000000000001
70 68.0 0.0 -9.75792485415751e-05 2.1869848057577132e-05 1.3944
71 69.0 0.0 -9.808686124612794e-05 1.946709148493532e-05 1.4071
72 70.0 0.0 -9.85391555004425e-05 1.7030409074934477e-05 1.42
73 71.0 0.0 -9.893425879655386e-05 1.4560646839220595e-05 1.4331
74 72.0 0.0 -9.927027233481838e-05 1.2058732544135474e-05 1.4464
75 73.0 0.0 -9.954527328671002e-05 9.525679307756304e-06 1.4599000000000002
76 74.0 0.0 -9.975731728143635e-05 6.962589231804475e-06 1.4736
77 75.0 0.0 -9.990444112681327e-05 4.370657060339902e-06 1.4875
78 76.0 0.0 -9.998466577493252e-05 1.7511738563136905e-06 1.5016
79 77.0 0.0 -9.999599954321475e-05 -8.944693138367792e-07 1.5159
80 78.0 0.0 -9.993644160145834e-05 -3.5647776983010223e-06 1.5304000000000002
81 79.0 0.0 -9.980398573546578e-05 -6.258149192448348e-06 1.5451000000000001
82 80.0 0.0 -9.95966243977505e-05 -8.972870698578612e-06 1.56
83 81.0 0.0 -9.931235305569338e-05 -1.1707114526701726e-05 1.5751
84 82.0 0.0 -9.894917484732254e-05 -1.4458934851294935e-05 1.5904
85 83.0 0.0 -9.850510555462879e-05 -1.722626424014905e-05 1.6059
86 84.0 0.0 -9.797817890399535e-05 -2.000691027262028e-05 1.6216
87 85.0 0.0 -9.736645220290827e-05 -2.279855226585184e-05 1.6375000000000002
88 86.0 0.0 -9.666801232161705e-05 -2.5598738128815886e-05 1.6536
89 87.0 0.0 -9.58809820278266e-05 -2.8404881365349675e-05 1.6699000000000002
90 88.0 0.0 -9.500352668181514e-05 -3.121425824871764e-05 1.6864000000000001
91 89.0 0.0 -9.403386129858073e-05 -3.4024005191618475e-05 1.7031
92 90.0 0.0 -9.297025798271502e-05 -3.683111633696989e-05 1.7200000000000002
93 91.0 0.0 -9.181105374067828e-05 -3.963244139623856e-05 1.7371
94 92.0 0.0 -9.055465867399797e-05 -4.242468376353232e-05 1.7544
95 93.0 0.0 -8.919956455562706e-05 -4.5204398935131514e-05 1.7719
96 94.0 0.0 -8.774435379026785e-05 -4.796799326559646e-05 1.7896
97 95.0 0.0 -8.618770875788803e-05 -5.071172309304299e-05 1.8075
98 96.0 0.0 -8.452842153791733e-05 -5.3431694267617555e-05 1.8256000000000001
99 97.0 0.0 -8.276540400970972e-05 -5.6123862118616924e-05 1.8439
100 98.0 0.0 -8.08976983227803e-05 -5.878403189707582e-05 1.8624
101 99.0 0.0 -7.892448772806883e-05 -6.140785973197496e-05 1.8811
102 100.0 0.0 -7.684510775903972e-05 -6.399085413949068e-05 1.9
103 101.0 0.0 -7.46590577487918e-05 -6.652837812589881e-05 1.9191
104 102.0 0.0 -7.236601266651652e-05 -6.901565192584626e-05 1.9384000000000001
105 103.0 0.0 -6.996583525360449e-05 -7.144775641869711e-05 1.9579
106 104.0 0.0 -6.745858843645298e-05 -7.381963726652448e-05 1.9776000000000002
107 105.0 0.0 -6.484454798956871e-05 -7.612610981804154e-05 1.9975
108 106.0 0.0 -6.212421541888612e-05 -7.836186482331717e-05 2.0176000000000003
109 107.0 0.0 -5.929833103133426e-05 -8.052147500448747e-05 2.0379
110 108.0 0.0 -5.6367887152580006e-05 -8.259940252782707e-05 2.0584000000000002
111 109.0 0.0 -5.333414145055902e-05 -8.459000742245954e-05 2.0791000000000004
112 110.0 0.0 -5.019863031787675e-05 -8.648755699063967e-05 2.1
113 111.0 0.0 -4.696318226142989e-05 -8.828623625390153e-05 2.1211
114 112.0 0.0 -4.36299312426667e-05 -8.998015947841043e-05 2.1424
115 113.0 0.0 -4.0201329906786734e-05 -9.156338282154988e-05 2.1639
116 114.0 0.0 -3.6680162633885195e-05 -9.302991814008938e-05 2.1856
117 115.0 0.0 -3.3069558339591776e-05 -9.437374799818187e-05 2.2075
118 116.0 0.0 -2.9373002947155928e-05 -9.558884191089653e-05 2.2296
119 117.0 0.0 -2.559435144721021e-05 -9.666917385597483e-05 2.2519
120 118.0 0.0 -2.1737839455627395e-05 -9.760874108296534e-05 2.2744
121 119.0 0.0 -1.7808094174002536e-05 -9.84015842448172e-05 2.2971000000000004
122 120.0 0.0 -1.3810144651370471e-05 -9.904180887235564e-05 2.3200000000000003
123 121.0 0.0 -9.749431239851467e-06 -9.952360820679386e-05 2.3431
124 122.0 0.0 -5.631814131040329e-06 -9.984128739951931e-05 2.3664
125 123.0 0.0 -1.4635808541675665e-06 -9.998928908179772e-05 2.3899
126 124.0 0.0 2.7485473885872958e-06 -9.996222029973469e-05 2.4136
127 125.0 0.0 6.997410679702419e-06 -9.975488080179127e-05 2.4375
128 126.0 0.0 1.1275406772107576e-05 -9.936229265734316e-05 2.4616000000000002
129 127.0 0.0 1.5574489043303514e-05 -9.87797311751759e-05 2.4859
130 128.0 0.0 1.98861661624192e-05 -9.800275708040388e-05 2.5104
131 129.0 0.0 2.4201503618818632e-05 -9.702724989706916e-05 2.5351
132 130.0 0.0 2.851112726430912e-05 -9.584944247160945e-05 2.56
133 131.0 0.0 3.2805229023794614e-05 -9.446595655947376e-05 2.5851
134 132.0 0.0 3.707357493126433e-05 -9.287383938341263e-05 2.6104000000000003
135 133.0 0.0 4.1305515649210165e-05 -9.107060105738215e-05 2.6359000000000004
136 134.0 0.0 4.548999962981594e-05 -8.905425275459531e-05 2.6616
137 135.0 0.0 4.9615589075450825e-05 -8.682334548205341e-05 2.6875
138 136.0 0.0 5.3670478854007505e-05 -8.43770093069287e-05 2.7136
139 137.0 0.0 5.764251852133727e-05 -8.171499286249302e-05 2.7399
140 138.0 0.0 6.151923759832183e-05 -7.883770294295253e-05 2.7664
141 139.0 0.0 6.528787424386095e-05 -7.574624397762439e-05 2.7931
142 140.0 0.0 6.893540745711695e-05 -7.24424571554642e-05 2.8200000000000003
143 141.0 0.0 7.244859293261136e-05 -6.8928958951117e-05 2.8471
144 142.0 0.0 7.58140026800881e-05 -6.52091787835393e-05 2.8744
145 143.0 0.0 7.901806850731089e-05 -6.128739551795232e-05 2.9019000000000004
146 144.0 0.0 8.204712944802844e-05 -5.716877250158922e-05 2.9295999999999998
147 145.0 0.0 8.488748319909571e-05 -5.2859390803557724e-05 2.9575
148 146.0 0.0 8.752544161007193e-05 -4.836628030934244e-05 2.9856000000000003
149 147.0 0.0 8.994739024541854e-05 -4.369744830122675e-05 3.0139000000000005
150 148.0 0.0 9.213985201359944e-05 -3.886190513744783e-05 3.0423999999999998
151 149.0 0.0 9.408955482885929e-05 -3.386968662546322e-05 3.0711
152 150.0 0.0 9.578350324015606e-05 -2.873187266857161e-05 3.1
153 151.0 0.0 9.720905392760702e-05 -2.3460601750585723e-05 3.1291
154 152.0 0.0 9.835399492984207e-05 -1.8069080810616793e-05 3.1584
155 153.0 0.0 9.920662842584688e-05 -1.2571590049629674e-05 3.1879
156 154.0 0.0 9.975585685224551e-05 -6.9834822026192765e-06 3.2176
157 155.0 0.0 9.999127209157403e-05 -1.3211758054129605e-06 3.2475000000000005
158 156.0 0.0 9.990324741902882e-05 4.397858016388089e-06 3.2776
159 157.0 0.0 9.948303184456377e-05 1.0155115706554476e-05 3.3079
160 158.0 0.0 9.87228464342335e-05 1.5931088849251602e-05 3.3384
161 159.0 0.0 9.761598213955617e-05 2.170529960470135e-05 3.3691000000000004
162 160.0 0.0 9.615689860666532e-05 2.7456344446183743e-05 3.4
163 161.0 0.0 9.434132337846043e-05 3.31619466135623e-05 3.4311000000000003
164 162.0 0.0 9.216635084322934e-05 3.879901767110548e-05 3.4624
165 163.0 0.0 8.963054022274018e-05 4.434372852365659e-05 3.4939
166 164.0 0.0 8.67340118320907e-05 4.9771590204761896e-05 3.5256
167 165.0 0.0 8.347854078322675e-05 5.5057544702821115e-05 3.5575
168 166.0 0.0 7.986764724463343e-05 6.017606603631379e-05 3.5896000000000003
169 167.0 0.0 7.590668231196969e-05 6.510127172636267e-05 3.6219
170 168.0 0.0 7.160290848913815e-05 6.980704474403788e-05 3.6544
171 169.0 0.0 6.696557372729566e-05 7.426716593067309e-05 3.6871
172 170.0 0.0 6.200597792154295e-05 7.845545680188934e-05 3.72
173 171.0 0.0 5.673753072246099e-05 8.234593254993105e-05 3.7531
174 172.0 0.0 5.117579948333757e-05 8.591296495431425e-05 3.7864
175 173.0 0.0 4.533854613496372e-05 8.913145479777476e-05 3.8199
176 174.0 0.0 3.924575175943646e-05 9.197701326329688e-05 3.8536
177 175.0 0.0 3.2919627623696125e-05 9.442615165894024e-05 3.8875
178 176.0 0.0 2.6384611433804805e-05 9.645647868073527e-05 3.9216000000000006
179 177.0 0.0 1.9667347583515437e-05 9.804690428070224e-05 3.9559
180 178.0 0.0 1.2796650196782336e-05 9.917784905784754e-05 3.9904
181 179.0 0.0 5.803447804817374e-06 9.98314579357467e-05 4.0251
+3 -2
View File
@@ -2,8 +2,8 @@ appdirs==1.4.4
cycler==0.10.0
future==0.18.2
kiwisolver==1.3.2
matplotlib==3.3.4
numpy==1.19.3
matplotlib==3.7.0
# numpy==1.19.3 ## do not include versioning to avoid versioning conflict
pandas==1.1.5
Pillow==8.4.0
pyparsing==2.4.7
@@ -12,3 +12,4 @@ python-dateutil==2.8.2
pytz==2021.3
scipy==1.7.1
six==1.16.0
screeninfo~=0.8.1
+662 -15
View File
@@ -3,7 +3,12 @@ import time
from datetime import datetime
from threading import Thread
import numpy as np
from numpy.lib.scimath import sqrt as csqrt
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkinter import LabelFrame
import scipy.optimize
from scipy import linalg as linalg_scipy
from src.utility import ui_print
from src.exceptions import DeviceBusy, DeviceAccessError
@@ -21,6 +26,7 @@ class AmbientFieldCalibration(Thread):
P_CONTROL = -7e3 # 0.2 A/s slew-rate at 40uT
I_CONTROL = 0 # -1e4 # 0.01A/s slew-rate for 1uTs
I_LIMIT = 1e-7 # uTs, Limit I to 0.025 A/s slew-rate to prevent wind-up
# D_CONTROL = Not implemented for now
def __init__(self, view_queue):
@@ -63,7 +69,7 @@ class AmbientFieldCalibration(Thread):
target_time = 0
current_time = datetime.now()
while (current_time - start_time).seconds < self.SETTLE_TIME:
# Each axis runs its own PID controller. They are slightly coupled by unorthogonality, which should
# Each axis runs its own PID controller. They are slightly coupled by non-orthogonality, which should
# hopefully not destabilize the feedback loop
for i in range(3):
# Error in tesla
@@ -71,11 +77,11 @@ class AmbientFieldCalibration(Thread):
e = g.MAGNETOMETER.field[i]
# Change in control current
du = e * self.P_CONTROL + self.error_integral[i] * self.I_CONTROL
self.axis_currents[i] += du*dt
self.axis_currents[i] += du * dt
# Update integral
# Add increment
self.error_integral[i] += e*dt
self.error_integral[i] += e * dt
# Clamp range
self.error_integral = np.clip(self.error_integral, -self.I_LIMIT, self.I_LIMIT)
@@ -149,7 +155,7 @@ class CoilConstantCalibration(Thread):
# All generated fields will be compared to this using a simple difference method
ambient_field = g.MAGNETOMETER.field
# This generates linearly spaced current setpoints and excludes zero
# This generates linearly spaced current set points and excludes zero
currents = np.linspace(-self.MEASUREMENT_RANGE, self.MEASUREMENT_RANGE, self.MEASUREMENT_POINTS * 2 + 1)
currents = np.delete(currents, self.MEASUREMENT_POINTS)
@@ -234,14 +240,17 @@ class CoilConstantCalibration(Thread):
self.view_queue.put({'cmd': command, 'arg': arg})
class MagnetometerCalibration(Thread):
TEST_VECTOR_MAGNITUDE = 100e-6 # In Tesla. Chosen so it can be achieved with a 3A PSU.
class MagnetometerCalibrationSimple(Thread):
def __init__(self, view_queue, calibration_points, calibration_interval):
def __init__(self, view_queue, calibration_points, calibration_interval, compensated_field, 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.compensated_field = compensated_field
self.matrix_trans_mgm_to_hh = [[x.get() for x in row] for row in mgm_to_helmholtz_cos_trans]
# 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.
@@ -281,8 +290,9 @@ class MagnetometerCalibration(Thread):
self.cage_dev.set_field_compensated([0, 0, 0])
# Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings
time.sleep(self.calibration_interval)
matrix_trans_mgm_to_hh_np = np.array(self.matrix_trans_mgm_to_hh)
# The offsets can easily be read from the magnetometer
offsets = g.MAGNETOMETER.field
offsets = matrix_trans_mgm_to_hh_np.dot(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]})
@@ -299,14 +309,17 @@ class MagnetometerCalibration(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
self.cage_dev.set_field_raw(applied_vec)
applied_vec = test_vec * self.calibration_mag_field
if not self.compensated_field:
self.cage_dev.set_field_raw(applied_vec) # Set raw field not compensated field
else:
self.cage_dev.set_field_compensated(applied_vec) # Set compensated field not raw field
# Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings
time.sleep(self.calibration_interval)
# Read output and save to array for solver later
raw_reading = g.MAGNETOMETER.field
raw_reading = matrix_trans_mgm_to_hh_np.dot(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]}
@@ -338,12 +351,13 @@ class MagnetometerCalibration(Thread):
b_e_x = g.CAGE_DEVICE.axes[0].ambient_field
b_e_y = g.CAGE_DEVICE.axes[1].ambient_field
b_e_z = g.CAGE_DEVICE.axes[2].ambient_field
b_e = sqrt(b_e_x**2 + b_e_y**2 + b_e_z**2)
b_e = sqrt(b_e_x ** 2 + b_e_y ** 2 + b_e_z ** 2)
# Perform least squares optimization on all magnetometer axes
sensor_parameters = []
for axis, axis_samples in enumerate(samples):
result = scipy.optimize.least_squares(self.residual_function, (1.0, pi/4, pi/4, pi/4), args=(b_e, axis_samples), gtol=1e-13)
result = scipy.optimize.least_squares(self.residual_function, (1.0, pi / 4, pi / 4, pi / 4),
args=(b_e, axis_samples), gtol=1e-13)
s, alpha_e, alpha, beta = result.x
residual = np.max(np.abs(result.fun))
sensor_parameters.append({'sensitivity': s,
@@ -368,7 +382,8 @@ class MagnetometerCalibration(Thread):
b_x = sample['b_x']
b_y = sample['b_y']
b_z = sample['b_z']
res.append(m - s * (b_e*sin(alpha_e) + b_x*cos(alpha)*cos(beta) + b_y*cos(alpha)*sin(beta) + b_z*sin(alpha)))
res.append(m - s * (b_e * sin(alpha_e) + b_x * cos(alpha) * cos(beta)
+ b_y * cos(alpha) * sin(beta) + b_z * sin(alpha)))
return res
@staticmethod
@@ -393,4 +408,636 @@ class MagnetometerCalibration(Thread):
return points
def put_message(self, command, arg):
self.view_queue.put({'cmd': command, 'arg': arg})
self.view_queue.put({'cmd': command, 'arg': arg})
class MagnetometerCalibrationComplete(Thread):
def __init__(self, view_queue, calibration_points, calibration_interval, calibration_mag_field,
calibration_oversampling, compensated_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.var_oversampling = calibration_oversampling
self.compensated_field = compensated_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
# 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.
# Make sure we really have magnetometer data
if not g.MAGNETOMETER.connected:
ui_print("\nError: The magnetometer is not connected. Required for ambient field calibration.")
raise DeviceAccessError("The magnetometer is not connected. Required for ambient field calibration.")
# Acquire cage device. This resource will only be released after the thread is ended.
try:
self.cage_dev = g.CAGE_DEVICE.request_proxy()
except DeviceBusy:
ui_print("\nError: Failed to acquire coil control. Required for ambient field calibration.")
raise DeviceAccessError("Failed to acquire coil control. Required for ambient field calibration.")
def run(self):
try:
self.calibration_procedure()
self.put_message('finished', None)
except Exception as e:
self.put_message('failed', e)
finally:
self.cage_dev.close()
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 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
raw_data = []
self.var_oversampling = 5
for i in range(self.var_oversampling):
# 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])
# Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings
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)
# Generate our set of test vectors
test_vectors = self.fibonacci_sphere(self.calibration_points)
# Holds the known variables for each row of our system of equations. These are M, B_x, B_y, B_z
# (B_E is constant for the test and not stored in the array)
# Each sensor axis has its own independent system of equations
samples = [[], [], []]
# Collect sensor data for each test vector
for vec_idx, test_vec in enumerate(test_vectors):
# Command output
applied_vec = test_vec * self.calibration_mag_field
if not self.compensated_field:
self.cage_dev.set_field_raw(applied_vec) # Set raw field not compensated field
else:
self.cage_dev.set_field_compensated(applied_vec) # Set compensated field not raw field
for i in range(self.var_oversampling):
# Sleep for a certain duration to allow psu to stabilize output and magnetometer to supply readings
time.sleep(self.calibration_interval)
# Read output and save to array for solver later
raw_reading = g.MAGNETOMETER.field
# reading = raw_reading - offsets
reading = raw_reading # Do not substract offset since it will artificially suppress (hard-iron) 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]}
# self.put_message("[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)
# Put device into an off and ready state
self.cage_dev.idle()
# Use collected data to build and solve system of equations
# sensor_parameters = self.solve_system(raw_data) # FLAG: untested!
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(
raw_data, self.matrix_trans_mgm_to_hh)
# Pass results to UI
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
self.put_message('progress', progress)
@staticmethod
def solve_system(raw_data, matrix_trans_mgm_to_hh_tk):
u_tesla = 10 ** 6
# Unpack data:
mag_x_set = np.zeros(len(raw_data))
mag_y_set = np.zeros(len(raw_data))
mag_z_set = np.zeros(len(raw_data))
mag_x_m = np.zeros(len(raw_data))
mag_y_m = np.zeros(len(raw_data))
mag_z_m = np.zeros(len(raw_data))
for i in range(len(raw_data)):
mag_x_set[i] = raw_data[i]['applied_x']
mag_y_set[i] = raw_data[i]['applied_y']
mag_z_set[i] = raw_data[i]['applied_z']
mag_x_m[i] = raw_data[i]['measured_x']
mag_y_m[i] = raw_data[i]['measured_y']
mag_z_m[i] = raw_data[i]['measured_z']
# Apply coordinate transformation from Helmholtz system to MGM system
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]
# 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}")
ui_print(matrix_trans_mgm_to_hh_np)
# Transform hh set magnetic field cos to sensor cos
mag_xyz_set = np.c_[mag_x_set, mag_y_set, mag_z_set]
mag_xyz_set_hh = np.matmul(matrix_trans_mgm_to_hh_np, mag_xyz_set.T).T
mag_x_set = mag_xyz_set_hh[:, 0]
mag_y_set = mag_xyz_set_hh[:, 1]
mag_z_set = mag_xyz_set_hh[:, 2]
# Calculate total error
err_x = 0
err_y = 0
err_z = 0
for i in range(1, len(mag_x_m)):
err_x += (mag_x_m[i] - mag_x_set[i]) ** 2
err_y += (mag_y_m[i] - mag_y_set[i]) ** 2
err_z += (mag_z_m[i] - mag_z_set[i]) ** 2
err_t = np.sqrt(err_x ** 2 + err_y ** 2 + err_z ** 2)
ui_print('Error in X/Y/Z/total: {:.2e} / {:.2e} / {:.2e} / {:.2e} [uT]'.format(err_x * u_tesla, err_y * u_tesla,
err_z * u_tesla,
err_t * u_tesla))
# Filter raw data
try:
mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m = \
MagnetometerCalibrationComplete.filter_magnetometer_data(mag_x_set, mag_y_set, mag_z_set,
mag_x_m, mag_y_m, mag_z_m)
except Warning as waring_filter_mgm_data:
ui_print(f"{waring_filter_mgm_data}")
# Get general magnitude of the set magnetic filed
mag_amp_set = np.sqrt(
mag_x_set[1:len(mag_x_set)] ** 2 + mag_y_set[1:len(mag_x_set)] ** 2 + mag_z_set[1:len(mag_x_set)] ** 2)
mag_amp_avg_set = np.mean(mag_amp_set)
mag_amp_m = np.sqrt(
mag_x_m[1:len(mag_x_m)] ** 2 + mag_y_m[1:len(mag_x_m)] ** 2 + mag_z_m[1:len(mag_x_m)] ** 2)
mag_amp_avg_m = np.mean(mag_amp_m)
# Calculate ellipsoid fit
try:
q_mat, n, d = MagnetometerCalibrationComplete.fit_ellipsoid(mag_x_m, mag_y_m, mag_z_m)
ui_print('Q matrix =')
ui_print(q_mat)
ui_print('n = [T]')
ui_print(n)
# Retrieve calibration parameters
q_mat_inv = np.linalg.inv(q_mat)
b = -np.dot(q_mat_inv, n)
# 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))
a_mat = np.linalg.inv(a_mat_inv)
# Calculate error
cal_x = np.zeros(mag_x_m.shape)
cal_y = np.zeros(mag_y_m.shape)
cal_z = np.zeros(mag_z_m.shape)
total_error = 0
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]
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
# 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))
ui_print('Soft-iron correction matrix a_mat_inv = ')
ui_print(a_mat_inv)
ui_print('Hard-iron offset b = [T]')
ui_print(b)
ui_print("Normalized total error E = {:4e} [0..1]]".format(total_error))
sensor_parameters = [{'a_mat': a_mat.tolist(),
'a_mat_inv': a_mat_inv.tolist(),
'b': b.tolist(),
'q_mat': q_mat.tolist(),
'n': n.tolist(),
'mag_amp_avg_set': mag_amp_avg_set.tolist(),
'total_error': total_error}]
return 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
except Warning as warning_message:
ui_print('A_inv could not be calculated! A warning occurred.')
ui_print('Please check if transformation matrix is input correctly.')
ui_print(f"{warning_message}")
except Exception as exception_message:
ui_print('A_inv could not be calculated! An unknown error occurred.')
ui_print('Please check if transformation matrix is input correctly.')
ui_print(f"{exception_message}")
@staticmethod
def filter_magnetometer_data(mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m):
flag_validity = np.ones(len(mag_x_set))
# Issue 1: Sometimes, the old and new measurement are very similar.
# The set magnetic field has not changed fast enough.
for i in range(1, len(mag_x_set)):
# Filter entry if all measurement results are very close to one another
if mag_x_m[i - 1] != 0:
div_x_m = mag_x_m[i] / mag_x_m[i - 1]
else:
div_x_m = 1
if mag_y_m[i - 1] != 0:
div_y_m = mag_y_m[i] / mag_y_m[i - 1]
else:
div_y_m = 1
if mag_z_m[i - 1] != 0:
div_z_m = mag_z_m[i] / mag_z_m[i - 1]
else:
div_z_m = 1
if mag_x_set[i - 1] != 0:
div_x_set = mag_x_set[i] / mag_x_set[i - 1]
else:
div_x_set = 1
if mag_y_set[i - 1] != 0:
div_y_set = mag_y_set[i] / mag_y_set[i - 1]
else:
div_y_set = 1
if mag_z_set[i - 1] != 0:
div_z_set = mag_z_set[i] / mag_z_set[i - 1]
else:
div_z_set = 1
bound_low = 0.9
bound_up = 1.1
if bound_low < div_x_m < bound_up and not bound_low < div_x_set < bound_up:
flag_validity[i] = 0
if bound_low < div_y_m < bound_up and not bound_low < div_y_set < bound_up:
flag_validity[i] = 0
if bound_low < div_z_m < bound_up and not bound_low < div_z_set < bound_up:
flag_validity[i] = 0
# Filter entry if signs of measurement result and set result are different
thresh = 70.0e-06 # [T] threshold value, difference in magnetic field component too large to be reasonable
if np.abs(mag_x_set[i] - mag_x_m[i]) > thresh or np.abs(mag_y_set[i] - mag_y_m[i]) > thresh or np.abs(
mag_z_set[i] - mag_z_m[i]) > thresh:
flag_validity[i] = 0
# ui_print("Ix {} X_set: {:.2e}, X_m: {:.2e}, Y_set: {:.2e}, Y_m: {:.2e}, Z_set: {:.2e}, Z_m: {:.2e}"
# .format(i, mag_x_set[i], mag_x_m[i], mag_y_set[i], mag_y_m[i], mag_z_set[i], mag_z_m[i]))
# ui_print("Ix {} X_set-X_m: {:.2e}, Y_set-Y_m: {:.2e}, Z_set-Z_m: {:.2e}"
# .format(i, np.abs(mag_x_set[i]-mag_x_m[i]), np.abs(mag_y_set[i]-mag_y_m[i]), np.abs(mag_z_set[i]-mag_z_m[i])))
# Issue: first element is zero-field measurement and always zero -> might lead to issues during calculation
flag_validity[0] = 0
# Delete faulty measurements from arrays
n_zeros = np.count_nonzero(flag_validity == 0)
ix_del = np.zeros(n_zeros)
j = 0
for i in range(0, len(mag_x_set)):
if flag_validity[i] == 0:
ix_del[j] = int(i - 0)
j = j + 1
ui_print('{} flagged measurements with indices {} removed.'.format(len(ix_del), ix_del))
mag_x_set = np.delete(mag_x_set, ix_del.astype(int), 0)
mag_y_set = np.delete(mag_y_set, ix_del.astype(int), 0)
mag_z_set = np.delete(mag_z_set, ix_del.astype(int), 0)
mag_x_m = np.delete(mag_x_m, ix_del.astype(int), 0)
mag_y_m = np.delete(mag_y_m, ix_del.astype(int), 0)
mag_z_m = np.delete(mag_z_m, ix_del.astype(int), 0)
n_filtered = len(mag_x_set)
if n_filtered < 5:
raise Warning("Filtered vectors only contains less than 5 Elements, filter not applied!")
else:
return mag_x_set, mag_y_set, mag_z_set, mag_x_m, mag_y_m, mag_z_m
@staticmethod
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
a2 = mag_y_m ** 2
a3 = mag_z_m ** 2
a4 = 2 * np.multiply(mag_y_m, mag_z_m)
a5 = 2 * np.multiply(mag_x_m, mag_z_m)
a6 = 2 * np.multiply(mag_x_m, mag_y_m)
a7 = 2 * mag_x_m
a8 = 2 * mag_y_m
a9 = 2 * mag_z_m
a10 = np.ones(len(mag_x_m)).T
d_mat = np.array([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])
# Equation 7, k = 4
c1 = 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]])
# Equation 11
s_mat = np.matmul(d_mat, d_mat.T)
s11 = s_mat[:6, :6]
s12 = s_mat[:6, 6:]
s21 = s_mat[6:, :6]
s22 = s_mat[6:, 6:]
# Equation 15, find eigenvalue and vector
# Since s_mat is symmetric, s12.T = s21
tmp = np.matmul(np.linalg.inv(c1), s11 - np.matmul(s12, np.matmul(np.linalg.inv(s22), s21)))
eigen_value, eigen_vector = np.linalg.eig(tmp)
u1 = eigen_vector[:, np.argmax(eigen_value)]
# Equation 13 solution
u2 = np.matmul(-np.matmul(np.linalg.inv(s22), s21), u1)
# Total solution
u = np.concatenate([u1, u2]).T
q_mat = np.array([[u[0], u[5], u[4]],
[u[5], u[1], u[3]],
[u[4], u[3], u[2]]])
n = np.array([[u[6]],
[u[7]],
[u[8]]])
d = u[9]
return q_mat, n, d
@staticmethod
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
@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):
plot_fontsize = 5
ax_width = 0.2
# Plot frame (overwrite plotframe)
plot_frame = LabelFrame(target_column, text="Result plots:")
plot_frame.grid(row=1, column=0, padx=(100, 0), pady=20, sticky="nw")
# Plot calibrated results
fig1 = plt.figure('MGM_cal_complete_left', figsize=(2.5, 3), dpi=100)
fig1.clf() # clear figure from previous use
canvas1 = FigureCanvasTkAgg(fig1, plot_frame)
u_tesla = 10 ** 6 # Tesla to microTesla conversion factor
meas_no = len(mag_x_set) # Measurement number
# uncalibrated result plot
ax1 = fig1.add_subplot(221, projection='3d')
# ax1.clf()
ax1.set_xlabel(r'$B_x [{\mu}T]$', fontsize=plot_fontsize)
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.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=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=1, color='b',
label="$B_{raw}$ uncalibrated")
# plot sphere with magnitude
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
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',
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
ax2 = fig1.add_subplot(223, projection='3d')
ax2.set_xlabel(r'$B_x [\mu T]$', fontsize=plot_fontsize)
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.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=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=1, color='r',
label="$B_{cal,el}$ ellipsoid fit")
# plot sphere with magnitude
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
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',
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='right', fontsize=plot_fontsize)
ax4 = fig1.add_subplot(224)
ax4.axis('off')
ax4.legend([l3, l4], ["$B_{set}$", "$B_{cal,el}$"],
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)
# 2d_math plots
fig2 = plt.figure('MGM_cal_complete_right', figsize=(4, 3), dpi=100)
fig2.clf() # clear figure from previous use
canvas2 = FigureCanvasTkAgg(fig2, plot_frame)
# x panel
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}$')
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}$')
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}$')
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)
# Function passed to scipy for the optimization
@staticmethod
def residual_function(x, b_e, samples):
# Unpack vector. These unknown parameters are described in the calibration paper
s, alpha_e, alpha, beta = x
# Residual vector
res = []
for sample in samples:
# Unpack row coefficients:
m = sample['m']
b_x = sample['b_x']
b_y = sample['b_y']
b_z = sample['b_z']
res.append(m - s * (b_e * sin(alpha_e) + b_x * cos(alpha) * cos(beta)
+ b_y * cos(alpha) * sin(beta) + b_z * sin(alpha)))
return res
@staticmethod
def fibonacci_sphere(samples):
"""
Algorithm to generate roughly equally spaced points on a sphere
From https://stackoverflow.com/a/26127012"""
points = []
phi = pi * (3.0 - sqrt(5.0)) # golden angle in radians
for i in range(samples):
y = 1 - (i / float(samples - 1)) * 2 # y goes from 1 to -1
radius = sqrt(1 - y * y) # radius at y
theta = phi * i # golden angle increment
x = cos(theta) * radius
z = sin(theta) * radius
points.append(np.array([x, y, z]))
return points
def put_message(self, command, arg):
self.view_queue.put({'cmd': command, 'arg': arg})
+108 -24
View File
@@ -9,6 +9,7 @@ import numpy as np
from threading import *
from tkinter import messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from src.exceptions import DeviceBusy, DeviceAccessError
from src.utility import ui_print
@@ -60,23 +61,31 @@ class ExecCSVThread(Thread):
return self._stop_event.is_set()
def execute_sequence(self, array, delay, parent, controller):
# Initialize plot
ui_print("Initializing plots...")
figure, avx_lines = display_plot(parent)
# main execution method of the class
# runs through array with times and desired fields and commands test bench accordingly
# array format: [time (s), xField (T), yField (T), zField (T)]
ui_print("Initializing cage...")
self.cage_dev.idle() # sets outputs on PSUs to 0 and Arduino pins to LOW before starting
t_zero = time.time() # set reference time for start of run
# Check if everything is properly connected:
all_connected = (parent.xy_override.get() or g.CAGE_DEVICE.psu1 is not None) and\
(parent.z_override.get() or g.CAGE_DEVICE.psu2 is not None) and\
all_connected = (parent.xy_override.get() or g.CAGE_DEVICE.psu1 is not None) and \
(parent.z_override.get() or g.CAGE_DEVICE.psu2 is not None) and \
(parent.arduino_override.get() or g.CAGE_DEVICE.arduino is not None)
compensate_field = parent.compensated_field_var.get()
# True or False depending on devices status, checks for some devices may be overridden by user
if not all_connected:
ui_print("Required devices are not present, sequence aborted.")
messagebox.showwarning("Device Error!", "Required devices are not present, sequence aborted.")
return
ui_print("Starting csv replay...")
i = 0 # index of the current array row
while i < len(array):
if self.stopped or g.exit_flag:
@@ -86,9 +95,9 @@ class ExecCSVThread(Thread):
return
# while array is not finished, devices are connected, user has not cancelled and application is running
t = time.time() - t_zero # get time relative to start of run
target_t = array[i, 0] # Target execution time of data point
if t >= target_t: # time for this row has come
field_vec = array[i, 1:4] # extract desired field vector
ui_print("[{:5.3f}s] B=[{:.1f}, {:.1f}, {:.1f}]\u03BCT for t={:.2f}s".format(t,
@@ -96,13 +105,28 @@ class ExecCSVThread(Thread):
field_vec[1] * 1e6,
field_vec[2] * 1e6,
target_t))
self.cage_dev.set_field_compensated(field_vec) # send field vector to test bench
if compensate_field:
self.cage_dev.set_field_compensated(field_vec) # send field vector to test bench
else:
self.cage_dev.set_field_raw(field_vec) # send field vector to test bench
# log change to the log file if user has selected event logging in the Configure Logging window
logger = controller.pages[ui.ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test bench is commanded
logger.log_datapoint() # log data
# Update figure
try:
if abs(t - target_t) < 0.2:
for j in range(4):
avx_lines[j].set_data([t, t], [0, 1])
# print("The next line might crash the programm, outcomment if necessary")
parent.plot_canvas.draw() # equivalent to matplotlib.show()
else:
ui_print("Update rate of plot slows down field generation and is thus skipped.")
except DeviceAccessError as e:
ui_print("Failed to update figure: ", e)
i = i + 1 # next row
elif t <= target_t - delay - 0.02: # is there enough time to sleep before the next row?
@@ -112,13 +136,13 @@ class ExecCSVThread(Thread):
def read_csv_to_array(filepath): # convert a given csv file to a numpy array
# csv format: time (s); xField (T); yField (T); zField (T) (german excel)
# decimal or period commas. Do not use these characters as a thousands seperator!
# csv format: time [s], xField [T], yField [T], zField [T] (german excel)
# decimal or period commas. Do not use these characters as a thousand separator!
with open(filepath, 'r') as csv_file:
# Normalize seperators
csv_string = csv_file.read().replace(',', '.')
# Normalize separators
csv_string = csv_file.read()
# read csv file without column headers
file = pandas.read_csv(StringIO(csv_string), sep=';', decimal='.', header=0)
file = pandas.read_csv(StringIO(csv_string), sep=',', decimal='.', header=0)
array = file.to_numpy() # convert csv to array
return array
@@ -134,7 +158,7 @@ def check_array_ok(array):
data_point = array[row_idx, i + 1] # extract data for this axis from array
if data_point > max_val or data_point < min_val:
# Out of bounds
warnings.append({'row': row_idx+1, 'axis': g.AXIS_NAMES[i]})
warnings.append({'row': row_idx + 1, 'axis': g.AXIS_NAMES[i]})
# show warning pop-up if values are exceeding limits
nr_warnings = len(warnings)
@@ -149,31 +173,75 @@ def check_array_ok(array):
messagebox.showwarning("Value Limits Warning!", warning_msg)
def display_plot(parent): # create plot of fixed size (pixels) from array
# calculate available height for plot (in pixels):
height_others = 0 # initialize variable to calculate height of other widgets
for element in parent.row_elements: # go through all rows in the widget except the plot frame
height_others += element.winfo_height() # add up heights
# calculate available plot height:
height = parent.parent.winfo_height() - height_others - 50 # height of parent frame - other widgets - margin
width = min(parent.parent.winfo_width() - 100, 1100) # set width to available space but max. 1100
# Create plot
figure = plot_field_sequence(parent.sequence_array, width, height) # create figure to be displayed
# Clear previous plots first
try:
if parent.plot_canvas is not None:
parent.plot_canvas.get_tk_widget().destroy()
except Exception as e:
ui_print("Something went wrong while plotting csv data!", e)
messagebox.showerror("Error!", "Something went wrong while plotting csv data: \n%s" % e)
pass
axes = figure.axes
avx_lines = [axes[0].axvline(x=0, color="r"), axes[1].axvline(x=0, color="r"),
axes[2].axvline(x=0, color="r"), axes[3].axvline(x=0, color="r")]
# Show new plot
parent.plot_canvas = FigureCanvasTkAgg(figure, parent.plot_frame) # create canvas to draw figure on
# print("The next line might crash the programm, outcomment if necessary")
parent.plot_canvas.draw() # equivalent to matplotlib.show()
parent.plot_canvas.get_tk_widget().grid(row=0, column=0, sticky="nesw") # place canvas in the UI
return figure, avx_lines
def plot_field_sequence(array, width, height): # create plot of fixed size (pixels) from array
# ToDo (optional): polar plots, plots of angle...
fig_dpi = 100 # set figure resolution (dots per inch)
px = 1/fig_dpi # get pixel to inch size conversion
figure = plt.Figure(figsize=(width*px, height*px), dpi=fig_dpi) # create figure with correct size
px = 1 / fig_dpi # get pixel to inch size conversion
figure = plt.Figure(figsize=(width * px, height * px), dpi=fig_dpi) # create figure with correct size
# noinspection PyTypeChecker,SpellCheckingInspection
axes = figure.subplots(3, sharex=True, sharey=True, gridspec_kw={'hspace': 0.4}) # create subplots with shared axes
axes = figure.subplots(4, sharex=True, sharey=False, gridspec_kw={'hspace': 0.4}) # make subplots with shared axes
figure.suptitle("Magnetic Field Sequence") # set figure title
# modify data to show instantaneous jumps in field to reflect test bench operation
new_array = np.array([[0, 0, 0, 0]], dtype=float) # initialize modified array, zeros to show start from no fields
new_array = np.array([[0, 0, 0, 0, 0]], dtype=float) # initialize modified array, zeros to show start from no field
last_vals = [0, 0, 0] # [x,y,z] field values from last data point (zero here), used to create step in data
for row in array[:, 0:4]: # go through each row in the original array
length = len(array[:, 0])
max_length = 1000
if length > max_length:
n = round(length/max_length) # plot every n-th element
ui_print("Array contains {:d} elements more than the maximum amount of {:d}. "
"To improve display only every {:d} element is plotted!"
.format(length, max_length, n))
else:
n = 1 # plot every element
last_values = [0, 0, 0, 0] # [x,y,z, rr] field values / rot rate from last data point (zero here)
for row in array[::n, 0:5]: # go through each row in the original array
# create extra datapoint at current timestamp, with field values from last to create a "step" in the plot:
new_array = np.append(new_array, [[row[0], *last_vals]], axis=0)
new_array = np.append(new_array, [[row[0], *last_values]], axis=0)
new_array = np.append(new_array, [row], axis=0) # add actual datapoint for current timestamp
last_vals = row[1:4] # save values from current timestamp for next
new_array = np.append(new_array, [[new_array[-1, 0], 0, 0, 0]], axis=0) # append last datapoint with 0 fields
last_values = row[1:5] # save values from current timestamp for next
new_array = np.append(new_array, [[new_array[-1, 0], 0, 0, 0, 0]], axis=0) # append last datapoint with 0 fields
# extract data and plot:
# extract data and plot magnetic fields:
t = new_array[:, 0] # extract time column
for i in [0, 1, 2]: # go through all three axes
for i in [0, 1, 2]: # go through all three axes plus rotation rate
data = new_array[:, i + 1] * 1e6 # extract field column of this axis and convert to microtesla
min_val, max_val = g.CAGE_DEVICE.axes[i].max_comp_field * 1e6 # get limits of achievable field
plot = axes[i] # get appropriate subplot
@@ -189,9 +257,25 @@ def plot_field_sequence(array, width, height): # create plot of fixed size (pix
plot.text(t[-1], min_val, "min", horizontalalignment='center', color='r')
plot.set_title(g.AXIS_NAMES[i], size=10) # set subplot title (e.g. "X-Axis")
plot.grid()
# plot rotation rate
plot = axes[3]
plot.plot(t, new_array[:, 4], linestyle='solid', marker='.') # plot data
plot.grid()
plot.set_title("Abs. Rotation Rate", size=10) # set subplot title (e.g. "X-Axis")
# set ylim of magnetic field axis to same value
ylim_mag = ([min(axes[0].get_ylim()), max(axes[0].get_ylim()),
min(axes[1].get_ylim()), max(axes[1].get_ylim()),
min(axes[2].get_ylim()), max(axes[2].get_ylim())])
for i in range(3):
axes[0].set_ylim(min(ylim_mag), max(ylim_mag))
# set shared axis labels:
axes[2].set_xlabel("Time (s)")
axes[1].set_ylabel("Magnetic Field (\u03BCT)")
axes[3].set_xlabel("Time [s]")
axes[1].set_ylabel("Magnetic Field [\u03BCT]")
axes[3].set_ylabel("Rate [°/s]")
# set y axis limits
for i in range(4):
axes[i].set_xlim(left=-round((new_array[-1, 0]+0.5)*0.01), right=round((new_array[-1, 0]+0.5)*1.01))
return figure # return the created figure to be inserted somewhere
+5
View File
@@ -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
+3
View File
@@ -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
+1255 -101
View File
File diff suppressed because it is too large Load Diff
+38 -2
View File
@@ -1,6 +1,6 @@
import csv
from tkinter import filedialog
import numpy as np
import src.globals as g
@@ -31,4 +31,40 @@ def save_dict_list_to_csv(filename, data, query_path=False):
csv_writer.writeheader()
for row in data:
csv_writer.writerow(row)
print(row)
csv_writer.writerow(row)
def save_dict_list_to_csv2(filename, data, query_path=False):
"""Creates a csv file under the specified path containing one row for each dict in the list 'data'.
The file receives a header containing the keys of the first dict entry.
Each dict should use the same keys."""
if query_path:
filename = filedialog.asksaveasfilename(initialfile=filename, title="Select csv save location...",
filetypes=(("CSV", "*.csv"),))
with open(filename, mode='w', newline='') as csv_file:
fieldnames = data[0].keys()
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames, delimiter=',', quotechar='"',
quoting=csv.QUOTE_NONNUMERIC)
csv_writer.writeheader()
for row in data:
csv_writer.writerow(data[row]) # this line makes issues in original file
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
# Save data point to raw_data list
raw_data = []
for i in range(data.shape[0]):
raw_data.append({'applied_x': data[i][0], 'applied_y': data[i][1], 'applied_z': data[i][2],
'measured_x': data[i][3], 'measured_y': data[i][4], 'measured_z': data[i][5]})
return data, raw_data, filename
Binary file not shown.
+18 -5
View File
@@ -1,11 +1,15 @@
import serial
import time
from datetime import datetime
import socket
# Lookup table for cage axis to magnetometer transformation
# First entry: Magnetometer y axis to cage x (reversed)
# axis_mapping = [[1, -1 ...]
axis_mapping = [[2, -1], [0, 1], [1, -1]]
# First entry: Magnetometer z axis to cage x (reversed) [1,1]
# axis_mapping = [[corresponding axis (0=x,1=y,2=z), direction (+-1) ...]
# axis_mapping = [[2, -1], [0, 1], [1, -1]] # Z_MGM = -X_HH, X_MGM = +Y_HH, Y_MGM = -Z_HH,
axis_mapping = [[0, -1], # X_MGM = -X_HH
[2, -1], # Y_MGM = -Z_HH
[1, -1]] # Z_MGM = -Y_HH,
# Helmholtz control software tcp port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -15,16 +19,25 @@ s.sendall("declare_api_version 2\n".encode())
# FGM3D software virtual serial port
ser = serial.Serial("COM11")
# check whether day time saving is current or not
if time.localtime().tm_isdst:
dst = 2
else:
dst = 1
line = b""
ready = False
while True:
line += ser.read()
if line[-2:] == b"\r\n":
new_line = line[:-2].decode('ascii')
delta = datetime.now().timestamp()*1000 - int(new_line.split(';')[0])+(2*60*60*1000)
delta = datetime.now().timestamp()*1000 - int(new_line.split(';')[0])+(dst*60*60*1000)
if delta < 500 and not ready:
ready = True
print("Program ready!")
print("FGM3D adapter script ready!")
elif not ready:
print("FGM3D adapter script not ready!")
if ready:
# Data is not valid otherwise
# Only use the x y and z values