''' example.py: opens and plots experimental data (channel impulse response measurements) Copyright (c) 2020 ETH Zurich, Anton Ledergerber Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' ############################################################# # python packages ############################################################# import sys, os import numpy as np import pandas as pd from scipy.interpolate import interp1d import matplotlib.pylab as plt ############################################################# # configurations ############################################################# FPOffset = -3 # offset of logged samples from first path sample as determined by Decawave's leading edge detection algorithm. numCIRSamples = 31 # number of logged CIR samples Ts = 1/(2*(4*124.8e6)) # (s) sampling period for CIR measurement anchorUWBIds = [0,1,2,4] # device Ids anchorVIds = [160, 161, 162, 164] # motion capture device Ids dynObjVId = 177 # motion capture dynamic object Id antennaOffset = np.array([0.14, 0.063, 0.010]) # (m) antenna offset from motion capture object body centerOffset = np.array([0.2775, 0.15, 0]) # (m) offset of center from motion capture dynamic object body lightSpeed = 299792458 # (m/s) speed of light ############################################################# # functions ############################################################# def quat_to_rot_mat(quat): """ convert quaternion (w,x,y,z) !hamiltonian notation! (ijk=-1) to rotation matrix Malcolm D. Shuster, A Survey of Attitude Representations, Equation (158) A rotation from frame A to frame B equals a coordinate transformation from A to B (Shuster Equation (87)) see also Joan Sola Quaternion kinematics for the error state KF for a good overview over the different conventions :param quat: Nx4 (hamiltonian notation) :return: Nx3x3 """ r0 = quat[:, 0]*quat[:, 0] r1 = quat[:, 1]*quat[:, 1] r2 = quat[:, 2]*quat[:, 2] r3 = quat[:, 3]*quat[:, 3] R00 = r0 + r1 - r2 - r3 R10 = 2 * quat[:, 1] * quat[:, 2] - 2 * quat[:, 0] * quat[:, 3] R20 = 2 * quat[:, 1] * quat[:, 3] + 2 * quat[:, 0] * quat[:, 2] R01 = 2 * quat[:, 1] * quat[:, 2] + 2 * quat[:, 0] * quat[:, 3] R11 = r0 - r1 + r2 - r3 R21 = 2 * quat[:, 2] * quat[:, 3] - 2 * quat[:, 0] * quat[:, 1] R02 = 2 * quat[:, 1] * quat[:, 3] - 2 * quat[:, 0] * quat[:, 2] R12 = 2 * quat[:, 2] * quat[:, 3] + 2 * quat[:, 0] * quat[:, 1] R22 = r0 - r1 - r2 + r3 R0 = np.stack((R00, R10, R20), axis=-1) R1 = np.stack((R01, R11, R21), axis=-1) R2 = np.stack((R02, R12, R22), axis=-1) R = np.stack((R0, R1, R2), axis=-1) return R ############################################################# # load logs ############################################################# # get directory of this file pythonFileName = sys.argv[0] pythonFilePath = os.path.dirname(pythonFileName) UWBLogFile = os.path.join(pythonFilePath,"UWBDefaultCH4.pkl") MCLogFile = os.path.join(pythonFilePath,"ViconDefaultCH4.pkl") UWBLog = pd.read_pickle(UWBLogFile) MCLog = pd.read_pickle(MCLogFile) ############################################################# # process UWB logs ############################################################# numUWBMeas = len(UWBLog) fp = np.array(UWBLog['fp_loc'], dtype=np.float) / 64 tOffset = (fp%1)*Ts*1e9 # (ns) FP offset with respect to FP sample accMem = np.zeros((numUWBMeas, numCIRSamples * 4), dtype=np.uint8) CIRReal = np.zeros((numUWBMeas, numCIRSamples)) CIRImag = np.zeros((numUWBMeas, numCIRSamples)) for j in range(numUWBMeas): accMem[j, :] = np.frombuffer(UWBLog['p_acc_mem'][j], dtype=np.uint8)[1:] # discard first byte for ii in range(numCIRSamples): CIRReal[:, ii] = np.int16(np.uint8(accMem[:, ii * 4]) + np.left_shift(np.uint8(accMem[:, ii * 4 + 1]), 8 * np.ones((accMem.shape[0],), dtype=np.int))) CIRImag[:, ii] = np.int16( np.uint8(accMem[:, ii * 4 + 2]) + np.left_shift(np.uint8(accMem[:, ii * 4 + 3]), 8 * np.ones((accMem.shape[0],), dtype=np.int))) accCount = UWBLog['rxpacc'] CIRReal = CIRReal / np.expand_dims(accCount, axis=-1) CIRImag = CIRImag / np.expand_dims(accCount, axis=-1) CIRMag = np.sqrt(CIRReal ** 2 + CIRImag ** 2) txId = UWBLog['txId'] txId = txId.to_numpy(dtype=np.int32) rxId = UWBLog['rxId'] rxId = rxId.to_numpy(dtype=np.int32) tUWB = UWBLog['time'] rxIds = np.unique(rxId) txIds = np.unique(txId) ######################################################## # process motion capture data ######################################################## # calculate antenna position and attitude avgAnchorPos = {} for vid in anchorVIds: # position of anchor vicon body in inertial frame anchorLog = MCLog.loc[MCLog['vid']==vid] pos = anchorLog[['x','y','z']].to_numpy() # transformation matrix from inertial frame to vicon body frame A_BI = quat_to_rot_mat(anchorLog[['q0','q1','q2','q3']].to_numpy()) A_IB = np.transpose(A_BI, axes=[0,2,1]) # position of anchor antenna in inertial frame pos = pos + np.matmul(A_IB, antennaOffset) avgAnchorPos[vid-160] = np.mean(pos[:,0:2], axis=0) # calculate dynamic object position and attitude dynObjLog = MCLog.loc[MCLog['vid']==dynObjVId] pos = dynObjLog[['x','y','z']].to_numpy() A_BI = quat_to_rot_mat(dynObjLog[['q0', 'q1', 'q2', 'q3']].to_numpy()) A_IB = np.transpose(A_BI, axes=[0, 2, 1]) dynObjPos = pos + np.matmul(A_IB, centerOffset) ######################################################## # synchronize motion capture data to UWB data ######################################################## # interpolate based on laptop reception time tInit = UWBLog['time'].iloc[0] tUWB = (tUWB-tInit).to_numpy().astype(dtype=np.float)*1e-9 tDynObjMC = (dynObjLog['time']-tInit).to_numpy().astype(dtype=np.float)*1e-9 # interpolate interpfpos_dynObj = interp1d(tDynObjMC, dynObjPos, kind='nearest', bounds_error=False, fill_value=(dynObjPos[0,:], dynObjPos[-1,:]), axis=0) posDynObjInterp = interpfpos_dynObj(tUWB) ############################################################# # plot accumulated CIR and corresponding target path location ############################################################# # select time, module Ids, and number of accumulated CIR measurements tUWB0 = 23 tx = 0 rx = 1 numAccCIRMeas = 50 # calculate corresponding target path location # based on motion capture data idxtUWB0 = np.searchsorted(tUWB, tUWB0) posDynObj0 = posDynObjInterp[idxtUWB0,0:2] posTx = avgAnchorPos[tx] posRx = avgAnchorPos[rx] distTxDynObj0 = np.linalg.norm(posDynObj0-posTx) distRxDynObj0 = np.linalg.norm(posDynObj0-posRx) distTxRx = np.linalg.norm(posTx-posRx) tau = (distRxDynObj0+distTxDynObj0-distTxRx)/lightSpeed*1e9 # select CIR measurements txRxMask = np.logical_and(tx==txId, rx==rxId) txRxIdx = np.nonzero(txRxMask)[0] idxTxRx0 = np.searchsorted(tUWB[txRxMask], tUWB0) idxTxRxAcc0 = txRxIdx[idxTxRx0:(idxTxRx0+numAccCIRMeas)] CIRSampleTimes = np.expand_dims(np.arange(start=FPOffset, stop=FPOffset+(numCIRSamples-0.5)*Ts*1e9, step=Ts*1e9, dtype=np.float), axis=0) tOffset = np.expand_dims(tOffset, axis=-1) plt.figure() plt.title('accumulated CIR at t={:.1f} for tx:{:} rx:{:}'.format(tUWB0, tx, rx)) plt.plot(np.transpose(CIRSampleTimes-tOffset[idxTxRxAcc0]), np.transpose(CIRMag[idxTxRxAcc0,:]), '.') plt.plot([tau, tau], [0, 60], 'k-', label='target path location') plt.legend() plt.xlabel('(ns)') plt.ylabel('(a.u.)') plt.show()