Using the MagNav Package with Real SGL Flight Data
This file is best viewed in a Pluto notebook. To run it this way, from the MagNav.jl directory, do:
julia> using Pluto
julia> Pluto.run() # select & open notebook
This is a reactive notebook, so feel free to change any parameters of interest.
Import packages and DataFrames
The DataFrames listed below provide useful information about the flight data collected by Sander Geophysics Ltd. (SGL) & magnetic anomaly maps.
Dataframe | Description |
---|---|
df_map | map files relevant for SGL flights |
df_cal | SGL calibration flight lines |
df_flight | SGL flight files |
df_all | all flight lines |
df_nav | all navigation-capable flight lines |
df_event | pilot-recorded in-flight events |
begin
cd(@__DIR__)
# uncomment line below to use local MagNav.jl (downloaded folder)
# using Pkg; Pkg.activate("../"); Pkg.instantiate()
using MagNav
using CSV, DataFrames
using Plots: plot, plot!
using Random: seed!
using Statistics: mean, median, std
seed!(33); # for reproducibility
include("dataframes_setup.jl"); # setup DataFrames
end;
Load flight and map data
Select Flight 1006 (see readme) & load the flight data. The full list of SGL flights is in df_flight
.
begin
flight = :Flt1006 # select flight, full list in df_flight
xyz = get_XYZ(flight,df_flight) # load flight data
end;
df_flight
flight | xyz_type | xyz_set | xyz_file | |
---|---|---|---|---|
1 | :Flt1002 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1002_train.h5" |
2 | :Flt1003 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1003_train.h5" |
3 | :Flt1004 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1004_train.h5" |
4 | :Flt1005 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1005_train.h5" |
5 | :Flt1006 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1006_train.h5" |
6 | :Flt1007 | :XYZ20 | 1 | "/home/runner/.julia/artifacts/0e129dcdd8b6f8b14581407e3f57ead6c82b24c2/sgl_2020_train/Flt1007_train.h5" |
7 | :Flt2001 | :XYZ21 | 1 | "/home/runner/.julia/artifacts/67c350d14287d4a3b2cdb75840fb466b36da9ad0/sgl_2021_train/Flt2001_train.h5" |
8 | :Flt2002 | :XYZ21 | 1 | "/home/runner/.julia/artifacts/67c350d14287d4a3b2cdb75840fb466b36da9ad0/sgl_2021_train/Flt2002_train.h5" |
9 | :Flt2004 | :XYZ21 | 1 | "/home/runner/.julia/artifacts/67c350d14287d4a3b2cdb75840fb466b36da9ad0/sgl_2021_train/Flt2004_train.h5" |
10 | :Flt2005 | :XYZ21 | 1 | "/home/runner/.julia/artifacts/67c350d14287d4a3b2cdb75840fb466b36da9ad0/sgl_2021_train/Flt2005_train.h5" |
... | ||||
16 | :Flt2017 | :XYZ21 | 1 | "/home/runner/.julia/artifacts/67c350d14287d4a3b2cdb75840fb466b36da9ad0/sgl_2021_train/Flt2017_train.h5" |
The xyz
flight data struct is of type MagNav.XYZ20
(for the 2020 SGL flight data collection), which is a subtype of MagNav.XYZ
(the abstract type for any flight data in MagNav.jl). There are 76 fields, which can be accessed using dot notation. These fields are described in the docs, which are easily accessed by searching MagNav.XYZ20
in the Live Docs in the lower right within Pluto.
typeof(xyz)
MagNav.XYZ20{Int64, Float64}
fieldnames(MagNav.XYZ20)
(:info, :traj, :ins, :flux_a, :flux_b, :flux_c, :flux_d, :flight, :line, :year, :doy, :utm_x, :utm_y, :utm_z, :msl, :baro, :diurnal, :igrf, :mag_1_c, :mag_1_lag, :mag_1_dc, :mag_1_igrf, :mag_1_uc, :mag_2_uc, :mag_3_uc, :mag_4_uc, :mag_5_uc, :mag_6_uc, :ogs_mag, :ogs_alt, :ins_wander, :ins_roll, :ins_pitch, :ins_yaw, :roll_rate, :pitch_rate, :yaw_rate, :ins_acc_x, :ins_acc_y, :ins_acc_z, :lgtl_acc, :ltrl_acc, :nrml_acc, :pitot_p, :static_p, :total_p, :cur_com_1, :cur_ac_hi, :cur_ac_lo, :cur_tank, :cur_flap, :cur_strb, :cur_srvo_o, :cur_srvo_m, :cur_srvo_i, :cur_heat, :cur_acpwr, :cur_outpwr, :cur_bat_1, :cur_bat_2, :vol_acpwr, :vol_outpwr, :vol_bat_1, :vol_bat_2, :vol_res_p, :vol_res_n, :vol_back_p, :vol_back_n, :vol_gyro_1, :vol_gyro_2, :vol_acc_p, :vol_acc_n, :vol_block, :vol_back, :vol_srvo, :vol_cabt, :vol_fan, :aux_1, :aux_2, :aux_3)
Select the map & view the flight line options (df_options
) for the selected flight & map. The full list of SGL flights is in df_flight
, the full list of maps is in df_map
, & the full list of navigation-capable flight lines is in df_nav
.
begin
map_name = :Eastern_395 # select map, full list in df_map
df_options = df_nav[(df_nav.flight .== flight ) .&
(df_nav.map_name .== map_name),:]
end
flight | line | t_start | t_end | full_line | map_name | map_type | line_alt | |
---|---|---|---|---|---|---|---|---|
1 | :Flt1006 | 1006.08 | 55770.0 | 56609.0 | true | :Eastern_395 | :HAE | 397 |
Select a flight line (row of df_options
) & get the flight data Boolean indices (mask).
begin
line = df_options.line[1] # select flight line (row) from df_options
ind = get_ind(xyz,line,df_nav) # get Boolean indices
# ind = get_ind(xyz;lines=[line]) # alternative
end;
Select a flight line (row of df_cal
) & get the flight data Boolean indices (mask) for Tolles-Lawson calibration. The full list of calibration flight line options is in df_cal
.
begin
TL_i = 6 # select first calibration box of 1006.04
TL_ind = get_ind(xyz;tt_lim=[df_cal.t_start[TL_i],df_cal.t_end[TL_i]])
end;
Baseline plots of scalar and vector (fluxgate) magnetometer data
Setup for the baseline plots.
begin
show_plot = false
save_plot = false
detrend_data = true
end;
Uncompensated (raw) scalar magnetometers.
The goal of compensation is to remove the magnetic field corruption caused by the platform (aircraft). Here, Magnetometer 1 is placed on a stinger (boom), & has low noise before compensation. Magnetometers 4 & 5 look like good candidates, while 3 is far more challenging.
b1 = plot_mag(xyz;ind,show_plot,save_plot,detrend_data,
use_mags = [:mag_1_uc,:mag_3_uc,:mag_4_uc,:mag_5_uc])
Vector (fluxgate) magnetometer d
.
b2 = plot_mag(xyz;ind,show_plot,save_plot,detrend_data,
use_mags = [:flux_d]) # try changing to :flux_a, :flux_b, :flux_c
Magnetometer 1 compensation as provided in dataset by SGL.
b3 = plot_mag(xyz;ind,show_plot,save_plot,
use_mags = [:comp_mags])
Magnetometer 1 compensation using Tolles-Lawson with vector (fluxgate) magnetometer d
.
b4 = plot_mag_c(xyz,xyz;ind,show_plot,save_plot,detrend_data,
ind_comp = TL_ind,
use_mags = [:mag_1_uc],
use_vec = :flux_d,
plot_diff = true)
Magnetometer 1 Welch power spectral density (PSD).
b5 = plot_frequency(xyz;ind,show_plot,save_plot,detrend_data,
field = :mag_1_uc,
freq_type = :PSD)
Magnetometer 1 spectrogram.
b6 = plot_frequency(xyz;ind,show_plot,save_plot,detrend_data,
field = :mag_1_uc,
freq_type = :spec)
Tolles-Lawson calibration and compensation
Fit the Tolles-Lawson coefficients for uncompensated scalar magnetometers 1-5
with vector (fluxgate) magnetometer d
.
begin
λ = 0.025 # ridge parameter for ridge regression
use_vec = :flux_d # selected vector (flux) magnetometer
flux = getfield(xyz,use_vec) # load Flux D data
TL_d_1 = create_TL_coef(flux,xyz.mag_1_uc,TL_ind;λ=λ) # Flux D & Mag 1
TL_d_2 = create_TL_coef(flux,xyz.mag_2_uc,TL_ind;λ=λ) # Flux D & Mag 2
TL_d_3 = create_TL_coef(flux,xyz.mag_3_uc,TL_ind;λ=λ) # Flux D & Mag 3
TL_d_4 = create_TL_coef(flux,xyz.mag_4_uc,TL_ind;λ=λ) # Flux D & Mag 4
TL_d_5 = create_TL_coef(flux,xyz.mag_5_uc,TL_ind;λ=λ) # Flux D & Mag 5
end;
Get the relevant scalar magnetometer data.
begin
A = create_TL_A(flux,ind)
mag_1_sgl = xyz.mag_1_c[ind]
mag_1_uc = xyz.mag_1_uc[ind]
mag_2_uc = xyz.mag_2_uc[ind]
mag_3_uc = xyz.mag_3_uc[ind]
mag_4_uc = xyz.mag_4_uc[ind]
mag_5_uc = xyz.mag_5_uc[ind]
end;
Create the Tolles-Lawson A
matrices & perform Tolles-Lawson compensation.
begin
mag_1_c = mag_1_uc - detrend(A*TL_d_1;mean_only=true)
mag_2_c = mag_2_uc - detrend(A*TL_d_2;mean_only=true)
mag_3_c = mag_3_uc - detrend(A*TL_d_3;mean_only=true)
mag_4_c = mag_4_uc - detrend(A*TL_d_4;mean_only=true)
mag_5_c = mag_5_uc - detrend(A*TL_d_5;mean_only=true)
end;
Prepare the flight data for the navigation filter, load the map data, & get the map interpolation function & map values along the selected flight line. The map values are then corrected for diurnal & core (IGRF) magnetic fields.
begin
traj = get_traj(xyz,ind) # trajectory (GPS) struct
ins = get_ins( xyz,ind;N_zero_ll=1) # INS struct, "zero" lat/lon to match first `traj` data point
mapS = get_map(map_name,df_map) # load map data
# get map values & map interpolation function
(map_val,itp_mapS) = get_map_val(mapS,traj;return_itp=true)
map_val += (xyz.diurnal + xyz.igrf)[ind] # add in diurnal & core (IGRF)
end;
Map to magnetometer (standard deviation) errors. Magnetometer 1
is great (stinger), while 2
is unusable & 3-5
are in-between.
begin
println("Mag 1: ",round(std(map_val-mag_1_c),digits=2))
println("Mag 2: ",round(std(map_val-mag_2_c),digits=2))
println("Mag 3: ",round(std(map_val-mag_3_c),digits=2))
println("Mag 4: ",round(std(map_val-mag_4_c),digits=2))
println("Mag 5: ",round(std(map_val-mag_5_c),digits=2))
end
Navigation
Create a navigation filter model. Only the most relevant navigation filter parameters are shown.
(P0,Qd,R) = create_model(traj.dt,traj.lat[1];
init_pos_sigma = 0.1,
init_alt_sigma = 1.0,
init_vel_sigma = 1.0,
meas_var = 5^2, # increase if mag_use is bad
fogm_sigma = 3,
fogm_tau = 180);
Run the navigation filter (EKF), determine the Cramér–Rao lower bound (CRLB), & extract output data.
begin
mag_use = mag_1_c # selected magnetometer, modify & see what happens
(crlb_out,ins_out,filt_out) = run_filt(traj,ins,mag_use,itp_mapS,:ekf;
P0,Qd,R,core=true)
end;
Plotting setup.
begin
t0 = traj.tt[1]/60 # [min]
tt = traj.tt/60 .- t0 # [min]
dpi = 200
end;
Compensated scalar magnetometers.
begin
p1 = plot(xlab="time [min]",ylab="magnetic field [nT]",legend=:topleft,dpi=dpi)
plot!(p1,tt,detrend(mag_1_uc ),lab="SGL raw Mag 1" ,color=:cyan,lw=2)
plot!(p1,tt,detrend(mag_1_sgl),lab="SGL comp Mag 1",color=:blue,lw=2)
plot!(p1,tt,detrend(mag_1_c ),lab="MIT comp Mag 1",color=:red ,lw=2,ls=:dash)
# plot!(p1,tt,detrend(mag_2_c ),lab="MIT comp Mag 2",color=:purple) # bad
plot!(p1,tt,detrend(mag_3_c ),lab="MIT comp Mag 3",color=:green)
plot!(p1,tt,detrend(mag_4_c ),lab="MIT comp Mag 4",color=:black)
plot!(p1,tt,detrend(mag_5_c ),lab="MIT comp Mag 5",color=:orange)
# png(p1,"comp_prof_1") # to save figure
end
Position (lat & lot) for trajectory (GPS), INS (after zeroing), & navigation filter.
begin
p2 = plot_map(mapS;map_color=:gray) # map background
plot_filt!(p2,traj,ins,filt_out;show_plot=false) # overlay GPS, INS, & filter
plot!(p2,legend=:topleft) # move as needed
end
Northing & easting INS error (after zeroing).
begin
p3 = plot(xlab="time [min]",ylab="error [m]",legend=:topleft,dpi=dpi)
plot!(p3,tt,ins_out.n_err,lab="northing")
plot!(p3,tt,ins_out.e_err,lab="easting")
end
(p4,p5) = plot_filt_err(traj,filt_out,crlb_out;show_plot,save_plot);
Northing navigation filter residuals.
p4
Easting navigation filter residuals.
p5
Map values vs magnetometer measurements.
p6 = plot_mag_map(traj,mag_use,itp_mapS)
In-flight events
Magnetometers with in-flight event(s) marked. This may be useful for understanding errors in the magnetic signal compared to the map. The full list of pilot-recorded in-flight events is in df_event
.
begin
p7 = plot(xlab="time [min]",ylab="magnetic field [nT]",dpi=dpi)
plot!(p7,tt,mag_1_uc,lab="mag_1_uc")
# plot!(p7,tt,mag_2_uc,lab="mag_2_uc") # bad
plot!(p7,tt,mag_3_uc,lab="mag_3_uc")
plot!(p7,tt,mag_4_uc,lab="mag_4_uc")
plot!(p7,tt,mag_5_uc,lab="mag_5_uc")
plot_events!(p7,flight,df_event;t0=t0,t_units=:min)
display(p7)
end