Skip to content

Commit 5c105eb

Browse files
author
evgkanias
committed
Add intensity gradient compass.
1 parent a3c2635 commit 5c105eb

3 files changed

Lines changed: 98 additions & 54 deletions

File tree

templates/plot_all.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import matplotlib.pyplot as plt
1212

1313
if __name__ == "__main__":
14-
data_dir = os.path.join(os.getcwd(),'../sardinia_data')
15-
out_base_dir = os.path.join(os.getcwd(),'../plots')
14+
data_dir = os.path.join(os.getcwd(), '../sardinia_data')
15+
out_base_dir = os.path.join(os.getcwd(), '../plots')
1616

1717
# Make plot directory if it doesn't exist. Existing files will
1818
# be overwritten.

templates/plot_from_dict.py

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

19+
1920
def act(s, log=True):
2021
"""
2122
Photoreceptor activation function. Gkanias et al. (2019) uses square
@@ -31,6 +32,7 @@ def act(s, log=True):
3132

3233
return np.log(s)
3334

35+
3436
def synchronise(d, zero=True, unwrap=True):
3537
"""
3638
Zero each angle series and adjust values so wraps plot nicely.
@@ -53,10 +55,9 @@ def synchronise(d, zero=True, unwrap=True):
5355

5456
return d
5557

56-
def decode_sky_compass(po,
57-
n_sol=8,
58-
pol_prefs=np.radians([0,90,180,270,45,135,225,315])
59-
):
58+
59+
def decode_sky_compass(po, n_sol=8, pol_prefs=np.radians([0, 90, 180, 270, 45, 135, 225, 315]),
60+
polarisation=True, intensity=False):
6061
"""
6162
Sky compass decoding routine from Gkanias et al. (2019). This has been
6263
implemeneted from scratch using the equations presented as a demonstration
@@ -77,65 +78,93 @@ def decode_sky_compass(po,
7778
# po[p][t][d] reading for diode d of unit p at time t
7879
units, duration, diodes = po.shape
7980

80-
n_pol = len(pol_prefs)
81-
sol_prefs = np.linspace(0,2*np.pi - (2*np.pi / n_sol),n_sol)
81+
sol_prefs = np.linspace(0, 2 * np.pi - (2 * np.pi / n_sol), n_sol)
8282

8383
angular_outputs = []
8484
confidence_outputs = []
8585

86-
for t in range(duration): # For each timestep
87-
# Get photodiode responses for each unit
88-
unit_responses = [ po[x][t] for x in range(units) ]
89-
ss = [ (s_h, s_v) for [_, _, s_v, s_h] in unit_responses ]
90-
rates = [ (act(h), act(v)) for (h, v) in ss ]
91-
r_pol = [ (r_h - r_v) / (r_h + r_v) for (r_h, r_v) in rates ]
92-
93-
# Init for sum
94-
r_sol = np.zeros(n_sol)
95-
R = 0
96-
for z in range(n_sol):
97-
for j in range(n_pol): # Compute SOL responses (Eq. (2))
98-
aj = pol_prefs[j] - np.pi/2
99-
r_sol[z] += (n_sol/n_pol) * np.sin(aj - sol_prefs[z]) * r_pol[j]
100-
101-
# Accumulate R (Eq. (3))
102-
R += r_sol[z]*np.exp(-1j*2*np.pi*(z - 1) / n_sol)
103-
104-
a = np.real(R)
105-
b = np.imag(R)
106-
107-
# Compute argument and magnitude of complex conjugate of R (Eq. (4))
108-
angle = np.arctan2(-b,a)
109-
confidence = np.sqrt(a**2 + b**2)
86+
for t in range(duration): # For each timestep
87+
88+
response = np.clip(po[:, t] / 12000., 0., 1.)
89+
if polarisation and not intensity:
90+
angle, sigma = pol2sol(response, sol_prefs, pol_prefs, unit_tranform=unit2pol)
91+
elif not polarisation and intensity:
92+
angle, sigma = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_tranform=unit2int)
93+
else:
94+
a_pol, c_pol = pol2sol(response, sol_prefs, pol_prefs, unit_tranform=unit2pol)
95+
a_int, c_int = pol2sol(response, sol_prefs + np.pi, pol_prefs, unit_tranform=unit2int)
96+
s_pol = 1. / c_pol ** 2
97+
s_int = 1. / c_int ** 2
98+
99+
angle = (s_pol * a_int + s_int * a_pol) / (s_pol + s_int)
100+
sigma = (s_pol * s_int) / (s_pol + s_int)
110101
angular_outputs.append(angle)
111-
confidence_outputs.append(confidence)
102+
confidence_outputs.append(sigma)
112103

113104
return angular_outputs, confidence_outputs
114105

115106

107+
def pol2sol(po, sol_prefs, pol_prefs, unit_tranform):
108+
units, diodes = po.shape
109+
n_sol = len(sol_prefs)
110+
n_pol = len(pol_prefs)
111+
112+
# Get photodiode responses for each unit
113+
r_pol = np.array([unit_tranform(po[x]) for x in range(units)])
114+
115+
# Init for sum
116+
r_sol = np.zeros(n_sol)
117+
R = 0
118+
for z in range(n_sol):
119+
for j in range(n_pol): # Compute SOL responses (Eq. (2))
120+
aj = pol_prefs[j] - np.pi / 2
121+
r_sol[z] += (n_sol / n_pol) * np.sin(aj - sol_prefs[z]) * r_pol[j]
122+
123+
# Accumulate R (Eq. (3))
124+
R += r_sol[z] * np.exp(-1j * 2 * np.pi * (z - 1) / n_sol)
125+
126+
a = np.real(R)
127+
b = np.imag(R)
128+
129+
# Compute argument and magnitude of complex conjugate of R (Eq. (4))
130+
angle = np.arctan2(-b, a)
131+
confidence = np.sqrt(a ** 2 + b ** 2)
132+
133+
return angle, confidence
134+
135+
136+
def unit2pol(unit_response, log=True):
137+
r_h, r_v = unit2vh(unit_response, log)
138+
return (r_h - r_v) / (r_h + r_v)
139+
140+
141+
def unit2int(unit_response, log=True):
142+
r_h, r_v = unit2vh(unit_response, log)
143+
r = (r_h + r_v) / 2
144+
return r
145+
146+
147+
def unit2vh(unit_response, log=True):
148+
_, _, s_v, s_h = unit_response
149+
return act(s_h, log), act(s_v, log)
150+
151+
116152
def produce_plot(data_dictionary):
117153

118-
mosaic = [["image","skycompass"],
119-
["image","imu_yaw"],
120-
["image","averages"]]
154+
mosaic = [["image", "skycompass"],
155+
["image", "imu_yaw"],
156+
["image", "averages"]]
121157

122-
fig, ax = plt.subplot_mosaic(mosaic, figsize=(10,4))
158+
fig, ax = plt.subplot_mosaic(mosaic, figsize=(10, 4))
123159

124160
#
125161
# Plot image if available
126162
#
127-
imagefile = data_dictionary["image_filename"]
128-
del data_dictionary["image_filename"]
129-
if imagefile != None:
130-
img = mpimg.imread(imagefile)
131-
ax["image"].imshow(img)
132-
ax["image"].set_title(imagefile.split(".")[0].replace("_", " "))
133-
else:
134-
ax["image"].set_title("No image found")
163+
plot_image(data_dictionary["image_filename"], ax=ax["image"])
135164

136-
max_length = 0 # Maximum recording length in set
137-
min_length = 0 # Minimum recording length in set
138-
first = True # First iteration flag
165+
max_length = 0 # Maximum recording length in set
166+
min_length = 0 # Minimum recording length in set
167+
first = True # First iteration flag
139168

140169
# Corrected datastructures for computing averages
141170
corrected_skycompass_table = []
@@ -152,7 +181,7 @@ def produce_plot(data_dictionary):
152181
po.append(data[k])
153182

154183
po = np.array(po)
155-
pol_sensor_angle = synchronise(np.array(decode_sky_compass(po)[0]))
184+
pol_sensor_angle = synchronise(np.array(decode_sky_compass(po, polarisation=True, intensity=True)[0]))
156185

157186
if max_length < len(imu):
158187
max_length = len(imu)
@@ -212,3 +241,16 @@ def produce_plot(data_dictionary):
212241

213242
return fig
214243

244+
245+
def plot_image(imagefile, ax=None):
246+
247+
if ax is None:
248+
ax = plt.subplot(111)
249+
250+
if imagefile is not None:
251+
img = mpimg.imread(imagefile)
252+
ax.imshow(img)
253+
ax.set_title(imagefile.split(".")[0].replace("_", " "))
254+
else:
255+
ax.set_title("No image found")
256+

templates/plot_json.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
import plot_from_dict as pfd
1717

18+
1819
def plot_json(session, outfile=None):
19-
"""
20+
"""
2021
Produce a figure along with a local output filename. For the output filename
2122
the caller must specify any preceeding directory structure, otherwise the
2223
output file will be relative to the calling directory.
@@ -33,6 +34,7 @@ def plot_json(session, outfile=None):
3334
#
3435
# Pull in data
3536
#
37+
3638
if not os.path.isdir(session):
3739
print("-s must specify a directory")
3840
sys.exit()
@@ -46,7 +48,6 @@ def plot_json(session, outfile=None):
4648
print("There are no json files in the specified directory.")
4749
sys.exit()
4850

49-
5051
if len(imagefile) == 0:
5152
print("Warning: missing sky image file from the session directory.")
5253
imagefile = None
@@ -56,14 +57,14 @@ def plot_json(session, outfile=None):
5657
imagefile = imagefile[0]
5758
# If outfile not specified, set to session spec
5859
if outfile == None:
59-
outfile = imagefile.split(".")[0] + ".pdf"
60+
outfile = imagefile.split(".")[0] + ".png"
6061

6162
# No output file specified and no/multiple image files
6263
# Use session directory name as pdf name
6364
if outfile == None:
6465
session_path = session.split("/")
6566
print(session_path)
66-
outfile = session_path[len(session_path) - 2] + ".pdf"
67+
outfile = session_path[len(session_path) - 2] + ".png"
6768

6869
# Pull all recordings into dictionary
6970
full_data = dict()
@@ -78,7 +79,8 @@ def plot_json(session, outfile=None):
7879
fig = pfd.produce_plot(full_data)
7980
return fig, outfile
8081

81-
if __name__=="__main__":
82+
83+
if __name__ == "__main__":
8284
calling_directory = os.getcwd()
8385
parser = argparse.ArgumentParser(
8486
description="Produce a recording-session plot from json."

0 commit comments

Comments
 (0)