1616import os
1717from scipy .stats import circmean
1818
19+
1920def 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+
3436def 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+
116152def 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+
0 commit comments