2023-10-22 14:40:10 +10:00
|
|
|
# Aero4200-Ass3
|
2023-10-22 17:25:20 +10:00
|
|
|
# Cal Wing, Sem 2 2023
|
2023-10-22 14:40:10 +10:00
|
|
|
|
2023-10-22 17:25:20 +10:00
|
|
|
# Import System / Common Libs
|
2023-10-22 20:23:11 +10:00
|
|
|
import os, pickle, time
|
2023-10-22 14:40:10 +10:00
|
|
|
import numpy as np
|
2023-10-22 17:25:20 +10:00
|
|
|
import pandas as pd
|
2023-10-22 14:40:10 +10:00
|
|
|
from tqdm import tqdm
|
|
|
|
|
2023-10-22 18:05:38 +10:00
|
|
|
from numpy import pi, sin, cos, tan
|
|
|
|
|
2023-10-22 20:23:11 +10:00
|
|
|
#Import makeGraph, a custom graphing wrapper I developed, refer to it for documentation
|
2023-10-22 14:40:10 +10:00
|
|
|
from makeGraph import makeGraph, pltKeyClose, UQ_COLOURS as UQC # Custom Graphing Lib
|
|
|
|
|
|
|
|
# Make sure the relevant folders folder exists
|
2023-10-22 19:05:34 +10:00
|
|
|
folders = ["./images", "./tmp"]
|
2023-10-22 14:40:10 +10:00
|
|
|
for folder in folders:
|
|
|
|
if not os.path.isdir(folder): os.mkdir(folder)
|
|
|
|
|
2023-10-22 20:23:11 +10:00
|
|
|
# This is a cacheing function, it checks if a cache file exists and loads it or it calcs & saves it.
|
|
|
|
# TL;DR This takes a file path & either a single variable (defArg) or some sort of callable and either
|
|
|
|
# loads it or dumps it to disk. Note: It does not validate the data inside!
|
2023-10-22 19:36:56 +10:00
|
|
|
def cacheData(dataFilePath: str, calcFunction: callable = lambda x: x, args: tuple = (), kargs: dict = {}, defArg=None):
|
|
|
|
if len(args) == 0 and defArg is not None:
|
|
|
|
args = (defArg, )
|
2023-10-22 19:05:34 +10:00
|
|
|
data = None
|
|
|
|
dataFileExt = dataFilePath.rsplit(".")[-1]
|
|
|
|
|
|
|
|
# Check if file exists
|
|
|
|
if os.path.isfile(dataFilePath):
|
2023-10-22 19:36:56 +10:00
|
|
|
print(f"Found data file \"{dataFilePath}\", loading data.")
|
2023-10-22 19:05:34 +10:00
|
|
|
# Check if file is a compressed numpy file
|
|
|
|
if dataFileExt == "npz":
|
2023-10-22 19:36:56 +10:00
|
|
|
data = np.load(dataFilePath, allow_pickle=True)["arr_0"]
|
2023-10-22 19:05:34 +10:00
|
|
|
elif dataFileExt == "pkl":
|
|
|
|
#If its not then just pickle it normally
|
|
|
|
with open(dataFilePath, 'rb') as handle:
|
|
|
|
data = pickle.load(handle)
|
2023-10-22 19:36:56 +10:00
|
|
|
elif dataFileExt == "dfz":
|
|
|
|
data = pd.read_pickle(dataFilePath, compression="gzip")
|
2023-10-22 19:05:34 +10:00
|
|
|
else:
|
|
|
|
raise TypeError(f"Cannot determine file type of: {dataFilePath}")
|
|
|
|
else:
|
2023-10-22 19:36:56 +10:00
|
|
|
print(f"Could not find data file \"{dataFilePath}\", generating & saving data.")
|
2023-10-22 19:05:34 +10:00
|
|
|
# Calculate Value
|
|
|
|
data = calcFunction(*args, **kargs)
|
|
|
|
|
|
|
|
# Check if file is a compressed numpy file
|
|
|
|
if dataFileExt == "npz":
|
2023-10-22 19:36:56 +10:00
|
|
|
np.savez_compressed(dataFilePath, data, allow_pickle=True)
|
|
|
|
|
|
|
|
elif dataFileExt == "dfz":
|
|
|
|
data.to_pickle(dataFilePath, compression="gzip")
|
|
|
|
|
2023-10-22 19:05:34 +10:00
|
|
|
elif dataFileExt == "pkl":
|
|
|
|
#If its not then just pickle it normally
|
|
|
|
with open(dataFilePath, 'wb') as handle:
|
|
|
|
pickle.dump(data, handle)
|
|
|
|
else:
|
|
|
|
raise TypeError(f"Cannot determine file type of: {dataFilePath}")
|
|
|
|
|
|
|
|
if data is None: raise ValueError("Could not import or generate data requested")
|
|
|
|
return data
|
|
|
|
|
2023-10-22 20:23:11 +10:00
|
|
|
|
|
|
|
|
2023-10-22 17:25:20 +10:00
|
|
|
# IMU Data Loading
|
|
|
|
# I map in to a heading to add units / make things make more sense
|
|
|
|
## The gyroscopic body angular rates from the IMU are given:
|
|
|
|
# - WBE_1 (in rad/s) - the roll rate about the body-fixed x-axis
|
|
|
|
# - WBE_2 (in rad/s) - the pitch rate about the body-fixed y-axis
|
|
|
|
# - WBE_3 (in rad/s) - the yaw rate about the body-fixed z-axis
|
|
|
|
## Specific forces:
|
2023-10-22 19:05:34 +10:00
|
|
|
# - FSP_X (in m/s^2) - the specific force in the body-fixed x-direction
|
|
|
|
# - FSP_Y (in m/s^2) - the specific force in the body-fixed y-direction
|
|
|
|
# - FSP_Z (in m/s^2) - the specific force in the body-fixed z-direction
|
2023-10-22 18:05:38 +10:00
|
|
|
IMU_TIME_HEADER = ["Time [s]"]
|
2023-10-31 23:05:24 +10:00
|
|
|
IMU_WBE_HEADERS = ["WBE_1 [rad/s]", "WBE_2 [rad/s]", "WBE_3 [rad/s]"] # Roll, Pitch, Yaw - Rate
|
|
|
|
IMU_FSP_HEADERS = ["FSP_X [m/s^2]", "FSP_Y [m/s^2]", "FSP_Z [m/s^2]"] # Specific Force X, Y , Z
|
2023-10-22 18:05:38 +10:00
|
|
|
IMU_DATA_HEADER = IMU_TIME_HEADER + IMU_WBE_HEADERS + IMU_FSP_HEADERS
|
2023-10-22 17:25:20 +10:00
|
|
|
def importIMUData(mission, imu):
|
|
|
|
# If IMU is not a string, then convert based on bool eval where "H" == True
|
|
|
|
if type(imu) != str: imu = "H" if imu else "L"
|
|
|
|
|
2023-10-22 17:25:43 +10:00
|
|
|
data = pd.read_csv(
|
2023-10-22 17:25:20 +10:00
|
|
|
f"./data/IMU_M{str(mission) + str(imu)}.txt",
|
|
|
|
header=None, skiprows=1,
|
|
|
|
names=IMU_DATA_HEADER,
|
|
|
|
)
|
|
|
|
|
2023-10-22 17:25:43 +10:00
|
|
|
return data
|
|
|
|
|
2023-10-22 19:05:34 +10:00
|
|
|
# Load the Mission Data
|
2023-10-22 17:25:20 +10:00
|
|
|
m1_IMUData = importIMUData(1, 0), importIMUData(1, 1) #(L, H) Data
|
|
|
|
m2_IMUData = importIMUData(2, 0), importIMUData(2, 1)
|
|
|
|
|
2023-10-22 19:05:34 +10:00
|
|
|
# NED Translation & Force Functions
|
2023-10-22 18:05:38 +10:00
|
|
|
INIT_EULER_ANGLES = (0, 0, 0)
|
2023-10-31 23:05:24 +10:00
|
|
|
def attidude_rate_2NED(angles, euler_angles):
|
2023-10-22 18:05:38 +10:00
|
|
|
phi, theta, psi = euler_angles
|
|
|
|
p, q, r = angles
|
|
|
|
|
|
|
|
transMat = np.array([ [1, sin(phi)*tan(theta), cos(phi)*tan(theta) ],
|
|
|
|
[0, cos(phi), -sin(phi) ],
|
|
|
|
[0, sin(phi)/cos(theta), cos(phi)/cos(theta) ]
|
|
|
|
])
|
|
|
|
|
|
|
|
angleMat = np.array([ [p],
|
|
|
|
[q],
|
|
|
|
[r]
|
|
|
|
])
|
2023-10-22 17:25:20 +10:00
|
|
|
|
2023-10-22 18:05:38 +10:00
|
|
|
return np.matmul(transMat, angleMat)
|
|
|
|
|
|
|
|
def getNEDForces(NEDPos):
|
2023-10-31 23:05:24 +10:00
|
|
|
phi, theta, psi = NEDPos # Roll, Pitch, Yaw
|
2023-10-22 18:05:38 +10:00
|
|
|
|
2023-10-31 23:05:24 +10:00
|
|
|
R41 = np.array([ [cos(psi)*cos(theta), cos(psi)*sin(theta)*sin(phi)-sin(psi)*cos(phi), cos(psi)*sin(theta)*cos(phi)+sin(psi)*sin(phi)],
|
2023-10-22 18:05:38 +10:00
|
|
|
[sin(psi)*cos(theta), sin(psi)*sin(theta)*sin(phi)+cos(psi)*cos(phi), sin(psi)*sin(theta)*cos(phi)-cos(psi)*sin(phi)],
|
|
|
|
[-sin(theta), cos(theta)*sin(phi), cos(theta)*cos(phi) ]
|
|
|
|
])
|
|
|
|
|
2023-10-31 23:05:24 +10:00
|
|
|
return R41
|
2023-10-22 14:46:20 +10:00
|
|
|
|
2023-10-22 19:36:56 +10:00
|
|
|
# Calculate Mission Translation & Force Data
|
2023-10-22 20:23:11 +10:00
|
|
|
TRANS_DATA_HEADER = IMU_TIME_HEADER + IMU_WBE_HEADERS + ["Forces", "ForceSum", "ForceCumSum"]
|
2023-10-22 19:36:56 +10:00
|
|
|
def calculateTranslatedData(missionWBEData) -> pd.DataFrame:
|
2023-10-22 19:05:34 +10:00
|
|
|
print("Translating Motion & Calculating Resulting Forces")
|
2023-10-22 20:23:11 +10:00
|
|
|
translatedData = pd.DataFrame(columns=TRANS_DATA_HEADER)
|
2023-10-22 19:05:34 +10:00
|
|
|
for i in tqdm(range(len(missionWBEData))):
|
2023-10-31 23:05:24 +10:00
|
|
|
dataPoint = missionWBEData[IMU_WBE_HEADERS].iloc[i] # Get the time point data
|
|
|
|
trans = attidude_rate_2NED(dataPoint.values, INIT_EULER_ANGLES).flatten() # Translate to Net
|
2023-10-22 19:05:34 +10:00
|
|
|
forces = getNEDForces(trans)
|
2023-10-22 20:23:11 +10:00
|
|
|
forceSum = np.array([np.sum(forces[:,0].flatten()), np.sum(forces[:,1].flatten()), np.sum(forces[:,2].flatten())])
|
|
|
|
privForce = translatedData[TRANS_DATA_HEADER[5]].iloc[i-1] if i > 0 else [0, 0, 0]
|
|
|
|
forceCumSum = np.array([ forceSum[ii] + value for ii, value in enumerate(privForce) ])
|
2023-10-31 23:05:24 +10:00
|
|
|
translatedData.loc[i] = {
|
2023-10-22 20:23:11 +10:00
|
|
|
TRANS_DATA_HEADER[0]: missionWBEData[IMU_TIME_HEADER].iloc[i],
|
|
|
|
TRANS_DATA_HEADER[1]: trans[0],
|
|
|
|
TRANS_DATA_HEADER[2]: trans[1],
|
|
|
|
TRANS_DATA_HEADER[3]: trans[2],
|
|
|
|
TRANS_DATA_HEADER[4]: forces,
|
|
|
|
TRANS_DATA_HEADER[5]: forceSum,
|
|
|
|
TRANS_DATA_HEADER[6]: forceCumSum,
|
2023-10-22 19:05:34 +10:00
|
|
|
}
|
2023-10-31 23:05:24 +10:00
|
|
|
|
2023-10-22 19:05:34 +10:00
|
|
|
return translatedData
|
|
|
|
|
2023-10-22 14:40:10 +10:00
|
|
|
if __name__ == '__main__':
|
2023-10-22 19:05:34 +10:00
|
|
|
missionWBEData = m1_IMUData[0][IMU_TIME_HEADER + IMU_WBE_HEADERS]
|
2023-10-22 20:23:11 +10:00
|
|
|
translatedData = cacheData("./tmp/m1_transData.dfz", calculateTranslatedData, (missionWBEData,))
|
2023-10-31 23:05:24 +10:00
|
|
|
|
|
|
|
x = np.cumsum(translatedData[IMU_WBE_HEADERS[0]])
|
|
|
|
y = np.cumsum(translatedData[IMU_WBE_HEADERS[1]])
|
|
|
|
z = np.cumsum(translatedData[IMU_WBE_HEADERS[2]])
|
2023-10-22 20:23:11 +10:00
|
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
fig = plt.figure()
|
|
|
|
ax = plt.axes(projection='3d')
|
|
|
|
plots = translatedData[TRANS_DATA_HEADER[6]].values
|
2023-10-31 23:05:24 +10:00
|
|
|
#x = [p[0] for p in plots]
|
|
|
|
#y = [p[1] for p in plots]
|
|
|
|
#z = [p[2] for p in plots]
|
2023-10-22 20:23:11 +10:00
|
|
|
ax.plot3D(x, y, z)
|
2023-10-31 23:05:24 +10:00
|
|
|
ax.plot3D(x+5, y-2, z+1)
|
|
|
|
|
2023-10-22 20:23:11 +10:00
|
|
|
plt.show()
|
2023-10-22 18:05:38 +10:00
|
|
|
|
2023-10-22 20:23:11 +10:00
|
|
|
|
|
|
|
print("Complete")
|