Skip to content

Commit 0afde6a

Browse files
author
evgkanias
committed
Add the eigenvector model (still not working properly).
1 parent b1c1b78 commit 0afde6a

2 files changed

Lines changed: 134 additions & 28 deletions

File tree

templates/plot_bagfile.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
import plot_from_dict as pfd
1717
from rosbag_to_json import rosbag_to_dict
1818

19-
def plot_bagfile(session, outfile=None):
19+
20+
def plot_bagfile(session, outfile=None, mode="polarisation"):
2021
"""
2122
Produce a figure along with a local output filename. For the output filename
2223
the caller must specify any preceeding directory structure, otherwise the
@@ -38,6 +39,9 @@ def plot_bagfile(session, outfile=None):
3839
print("-s must specify a directory")
3940
sys.exit()
4041

42+
polarisation = "polarisation" in mode.lower()
43+
intensity = "intensity" in mode.lower()
44+
4145
calling_dir = os.getcwd()
4246
os.chdir(session)
4347
imagefile = [x for x in os.listdir() if ".jpg" in x]
@@ -56,14 +60,13 @@ def plot_bagfile(session, outfile=None):
5660
imagefile = imagefile[0]
5761
# If outfile not specified, set to session spec
5862
if outfile == None:
59-
outfile = imagefile.split(".")[0] + ".pdf"
63+
outfile = f"{imagefile.split('.')[0]}-{mode}.pdf"
6064

6165
# No output file specified and no/multiple image files
6266
# Use session directory name as pdf name
6367
if outfile == None:
64-
session_path = session.split("/")
65-
print(session_path)
66-
outfile = session_path[len(session_path) - 2] + ".pdf"
68+
session_path = os.path.abspath(session.split(os.path.sep))
69+
outfile = f"{session_path[len(session_path) - 2]}-{mode}.pdf"
6770

6871

6972
#
@@ -78,9 +81,10 @@ def plot_bagfile(session, outfile=None):
7881

7982
full_data["image_filename"] = imagefile
8083

81-
fig = pfd.produce_plot(full_data)
84+
fig = pfd.produce_plot(full_data, polarisation=polarisation, intensity=intensity)
8285
return fig, outfile
8386

87+
8488
if __name__=="__main__":
8589
calling_directory = os.getcwd()
8690
parser = argparse.ArgumentParser(
@@ -96,14 +100,26 @@ def plot_bagfile(session, outfile=None):
96100

97101
args = parser.parse_args()
98102

99-
session = args.session
103+
session = os.path.abspath(args.session)
100104
outfile = args.output
101105

102-
fig, outfile = plot_bagfile(session, outfile=outfile)
103-
104-
# Save files relative to calling directory unless absolute
105-
# path is specified.
106-
os.chdir(calling_directory)
107-
fig.savefig(outfile, bbox_inches="tight")
108-
plt.show()
106+
fig = None
107+
unit_angles = [
108+
[45.0, -45.0, 90.0, 0.0],
109+
[-45.0, 45.0, 90.0, 0.0],
110+
[-45.0, 45.0, 0.0, 90.0],
111+
[45.0, -45.0, 0.0, 90.0]
112+
]
113+
for ua in unit_angles:
114+
print("\t".join([f"{a:.0f}" for a in ua]), end="\t")
115+
pfd.UNIT_ANGLES[:] = ua
116+
pfd.update_globals()
117+
fig, outfile = plot_bagfile(session, outfile=outfile, mode="eigenvector")
118+
outfile = outfile.replace(".pdf", ".png")
119+
120+
# Save files relative to calling directory unless absolute
121+
# path is specified.
122+
# os.chdir(calling_directory)
123+
# fig.savefig(outfile, bbox_inches="tight")
124+
plt.show()
109125

templates/plot_from_dict.py

Lines changed: 104 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
import os
1717
from scipy.stats import circmean
1818

19+
UNIT_ANGLES = np.deg2rad([45, 135, 0, 90], dtype='float64') # -45, +45, 0, 90
20+
POL_PREFS = np.deg2rad([0, 90, 180, 270, 45, 135, 225, 315], dtype='float64')
21+
22+
A = None
23+
A_inv = A
24+
1925

2026
def act(s, log=True):
2127
"""
@@ -57,8 +63,7 @@ def synchronise(d, zero=True, unwrap=True):
5763
return d
5864

5965

60-
def decode_sky_compass(po, n_sol=8, pol_prefs=np.radians([0, 90, 180, 270, 45, 135, 225, 315]),
61-
polarisation=True, intensity=False):
66+
def decode_sky_compass(po, n_sol=8, pol_prefs=POL_PREFS, polarisation=True, intensity=False):
6267
"""
6368
Sky compass decoding routine from Gkanias et al. (2019). This has been
6469
implemeneted from scratch using the equations presented as a demonstration
@@ -87,15 +92,17 @@ def decode_sky_compass(po, n_sol=8, pol_prefs=np.radians([0, 90, 180, 270, 45, 1
8792
for t in range(duration): # For each timestep
8893

8994
# ensure that the response is in [0, 1]
90-
# the highest observed value was 11986 (Thursday 17:20)
91-
response = np.clip(po[:, t] / 12000., 0., 1.)
92-
if polarisation and not intensity:
93-
angle, sigma = pol2sol(response, sol_prefs, pol_prefs, unit_tranform=unit2pol)
95+
# the highest observed value was 32768 (Thursday 17:20)
96+
response = np.clip(po[:, t] / 33000., 0., 1.)
97+
if not polarisation and not intensity:
98+
angle, sigma = pol2eig(response, pol_prefs)
99+
elif polarisation and not intensity:
100+
angle, sigma = pol2sol(response, sol_prefs, pol_prefs, unit_transform=unit2pol)
94101
elif not polarisation and intensity:
95-
angle, sigma = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_tranform=unit2int)
102+
angle, sigma = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_transform=unit2int)
96103
else:
97-
a_pol, c_pol = pol2sol(response, sol_prefs, pol_prefs, unit_tranform=unit2pol)
98-
a_int, c_int = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_tranform=unit2int)
104+
a_pol, c_pol = pol2sol(response, sol_prefs, pol_prefs, unit_transform=unit2pol)
105+
a_int, c_int = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_transform=unit2int)
99106
s_pol = 1. / c_pol ** 2
100107
s_int = 1. / c_int ** 2
101108

@@ -107,13 +114,53 @@ def decode_sky_compass(po, n_sol=8, pol_prefs=np.radians([0, 90, 180, 270, 45, 1
107114
return angular_outputs, confidence_outputs
108115

109116

110-
def pol2sol(po, sol_prefs, pol_prefs, unit_tranform):
117+
def pol2eig(po, pol_prefs):
118+
119+
units, diodes = po.shape
120+
121+
# Get photodiode responses for each unit
122+
phi, d = np.array([unit2eig(po[x]) for x in range(units)]).T
123+
124+
pe = np.array([np.cos(phi + pol_prefs), np.sin(phi + pol_prefs), np.zeros_like(phi)])
125+
126+
alpha = -pol_prefs
127+
gamma = np.full_like(alpha, np.deg2rad(45))
128+
129+
c = np.zeros([3, 3, 8], dtype='float32')
130+
e = np.zeros_like(pe)
131+
for i, a, g in zip(range(len(alpha)), alpha, gamma):
132+
c[..., i] = np.array([[np.cos(a), -np.sin(a), 0], [np.sin(a), np.cos(a), 0], [0, 0, 1]])
133+
c[..., i] = np.dot(c[..., i], np.array([[np.cos(g), 0, np.sin(g)], [0, 1, 0], [-np.sin(g), 0, np.cos(g)]]))
134+
135+
e[:, i] = c[..., i].dot(pe[:, i])
136+
137+
eig = e.dot(e.T)
138+
139+
eigenvalues, eigenvectors = np.linalg.eigh(eig)
140+
141+
i_star = np.argmin(eigenvalues)
142+
s_1, s_2, s_3 = eigenvectors[i_star]
143+
144+
# print(eigenvalues)
145+
# print(eigenvectors)
146+
147+
# import sys
148+
# sys.exit()
149+
150+
# Compute argument and magnitude of complex conjugate of R (Eq. (4))
151+
angle = np.arctan2(s_2, s_1)
152+
confidence = 1.0
153+
154+
return angle, confidence
155+
156+
157+
def pol2sol(po, sol_prefs, pol_prefs, unit_transform):
111158
units, diodes = po.shape
112159
n_sol = len(sol_prefs)
113160
n_pol = len(pol_prefs)
114161

115162
# Get photodiode responses for each unit
116-
r_pol = np.array([unit_tranform(po[x]) for x in range(units)])
163+
r_pol = np.array([unit_transform(po[x]) for x in range(units)])
117164

118165
# Init for sum
119166
r_sol = np.zeros(n_sol)
@@ -136,23 +183,41 @@ def pol2sol(po, sol_prefs, pol_prefs, unit_tranform):
136183
return angle, confidence
137184

138185

186+
def unit2eig(unit_response):
187+
f = unit_response
188+
# q1, q2, q3 = np.linalg.inv(a.T.dot(a)).dot(a.T).dot(f)
189+
q1, q2, q3 = A_inv.dot(f)
190+
# q1 = i_0 - i_90
191+
# q2 = i_45 - i_135
192+
# q3 = (i_0 + i_45 + i_90 + i_135) / 2
193+
194+
phi = 0.5 * np.arctan2(q2, q1)
195+
d = np.sqrt(np.square(q1) + np.square(q2)) / q3
196+
197+
# print(f"phi = {np.rad2deg(phi):.2f}, d = {d:.2f}, responses = {unit_response}")
198+
199+
return phi, d
200+
201+
139202
def unit2pol(unit_response, log=True):
140203
r_h, r_v = unit2vh(unit_response, log)
141204
return (r_h - r_v) / (r_h + r_v)
142205

143206

144207
def unit2int(unit_response, log=True):
145208
r_h, r_v = unit2vh(unit_response, log)
146-
r = (r_h + r_v) / 2
209+
# r = (r_h + r_v) / 2
210+
r = np.sqrt(r_h**2 + r_v**2)
147211
return r
148212

149213

150214
def unit2vh(unit_response, log=True):
215+
# -45, +45, 0, 90
151216
_, _, s_v, s_h = unit_response
152217
return act(s_h, log), act(s_v, log)
153218

154219

155-
def produce_plot(data_dictionary):
220+
def produce_plot(data_dictionary, polarisation=True, intensity=False):
156221

157222
mosaic = [["image", "skycompass"],
158223
["image", "imu_yaw"],
@@ -174,6 +239,9 @@ def produce_plot(data_dictionary):
174239
corrected_skycompass_table = []
175240
corrected_imu_table = []
176241

242+
mse = []
243+
244+
print("MAE:", end=" ")
177245
for k in data_dictionary.keys():
178246
data = data_dictionary[k]
179247

@@ -185,7 +253,8 @@ def produce_plot(data_dictionary):
185253
po.append(data[k])
186254

187255
po = np.array(po)
188-
pol_sensor_angle = synchronise(np.array(decode_sky_compass(po, polarisation=True, intensity=False)[0]))
256+
pol_sensor_angle = synchronise(
257+
np.array(decode_sky_compass(po, polarisation=polarisation, intensity=intensity)[0]))
189258

190259
if max_length < len(imu):
191260
max_length = len(imu)
@@ -201,8 +270,12 @@ def produce_plot(data_dictionary):
201270
plot_angles(max_length, data=pol_sensor_angle, ax=ax["skycompass"], x_ticks=False)
202271
plot_angles(max_length, data=imu, ax=ax["imu_yaw"], x_ticks=False)
203272

273+
mse.append(compare(pol_sensor_angle, imu))
274+
print(f"{np.rad2deg(mse[-1]):.2f}", end="\t")
275+
204276
corrected_skycompass_table.append(pol_sensor_angle)
205277
corrected_imu_table.append(imu)
278+
print(f"\nMean MAE: {np.rad2deg(np.nanmean(mse)):.2f} +/- {np.rad2deg(np.nanstd(mse)):.2f}")
206279

207280
# Compute mean angle over all reacordings at each timestep.
208281
sc_means = []
@@ -275,3 +348,20 @@ def plot_angles(max_length, data=None, ax=None, x_padding=0, y_padding=np.pi/18,
275348
ax.set_xlim([-x_padding, max_length + x_padding - 1])
276349

277350
return ax
351+
352+
353+
def compare(x, ground_truth):
354+
ang_distance = (x - ground_truth + np.pi) % (2 * np.pi) - np.pi
355+
return np.mean(np.abs(ang_distance))
356+
357+
358+
def update_globals():
359+
global A, A_inv
360+
361+
A = np.round(np.array([np.cos(2 * UNIT_ANGLES), np.sin(2 * UNIT_ANGLES), np.ones_like(UNIT_ANGLES)]).T, decimals=2)
362+
363+
# A_inv = np.linalg.inv(A.T.dot(A)).dot(A.T)
364+
A_inv = np.linalg.pinv(A)
365+
366+
367+
update_globals()

0 commit comments

Comments
 (0)