Skip to content
This repository was archived by the owner on Jun 11, 2025. It is now read-only.

Commit 08ee14c

Browse files
authored
Merge pull request #64 from ltindall/delayed_data_handling
Now gracefully updates tracks despite skipping MQTT messages.
2 parents b5322f9 + 9c9fb0e commit 08ee14c

File tree

3 files changed

+104
-29
lines changed

3 files changed

+104
-29
lines changed

birdseye/env.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def __init__(self, sensor=None, actions=None, state=None, simulated=True):
2222
self.last_observation = None
2323
self.pf = None
2424

25-
def dynamics(self, particles, control=None, **kwargs):
25+
def dynamics(self, particles, control=None, distance=None, course=None, heading=None, **kwargs):
2626
"""Helper function for particle filter dynamics
2727
2828
Returns
@@ -34,7 +34,7 @@ def dynamics(self, particles, control=None, **kwargs):
3434
for p in particles:
3535
new_p = []
3636
for t in range(self.state.n_targets):
37-
new_p += self.state.update_state(p[4*t:4*(t+1)], control)
37+
new_p += self.state.update_state(p[4*t:4*(t+1)], control=control, distance=distance, course=course, heading=heading)
3838
#new_p = np.array([self.state.update_state(target_state, control) for target_state in p])
3939
updated_particles.append(new_p)
4040
return np.array(updated_particles)
@@ -92,22 +92,24 @@ def reset(self, num_particles=2000):
9292
return env_obs
9393

9494
# returns observation, reward, done, info
95-
def real_step(self, action, bearing):
96-
#action = self.actions.index_to_action(action_idx)
95+
def real_step(self, data):
96+
#action = data['action_taken'] if data.get('action_taken', None) else (0,0)
9797

9898
# Update position of sensor
99-
self.state.update_sensor(action, bearing=bearing)
99+
self.state.update_real_sensor(data.get('distance', None), data.get('course', None), data.get('bearing', None))
100100

101101
# Get sensor observation
102102
observation = self.sensor.real_observation()
103103
observation = np.array(observation) if observation is not None else None
104104

105105
# Update particle filter
106-
self.pf.update(observation, xp=self.pf.particles, control=action)
106+
self.pf.update(observation, xp=self.pf.particles, distance=data.get('distance', None), course=data.get('course', None), heading=data.get('bearing', None))
107107
#particle_swap(self)
108108

109109
# Calculate reward based on updated state & action
110-
reward = self.state.reward_func(state=None, action=action, particles=self.pf.particles)
110+
control_heading = data['bearing'] if data.get('bearing', None) else self.state.sensor_state[2]
111+
control_delta_heading = (control_heading - self.state.sensor_state[2]) % 360
112+
reward = self.state.reward_func(state=None, action=(control_delta_heading,data.get('distance',0)), particles=self.pf.particles)
111113

112114
belief_obs = self.env_observation()
113115

birdseye/state.py

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import random
22
import numpy as np
33
from scipy.ndimage.filters import gaussian_filter
4-
from .utils import pol2cart
4+
from .utils import pol2cart, cart2pol
55

66

77
class State:
@@ -49,7 +49,10 @@ def __init__(self, n_targets=1, prob=0.9, target_speed=None, target_speed_range=
4949
# Setup an initial random state
5050
self.target_state = None
5151
if simulated:
52+
self.update_state = self.update_sim_state
5253
self.target_state = self.init_target_state()
54+
else:
55+
self.update_state = self.update_real_state
5356
# Setup an initial sensor state
5457
self.sensor_state = self.init_sensor_state()
5558

@@ -241,7 +244,7 @@ def entropy_collision_reward(self, state, action=None, action_idx=None, particle
241244

242245

243246
# returns new state given last state and action (control)
244-
def update_state(self, state, control, target_update=False, transition_overwrite=None):
247+
def update_sim_state(self, state, control=None, transition_overwrite=None, **kwargs):
245248
"""Update state based on state and action
246249
247250
Parameters
@@ -258,6 +261,9 @@ def update_state(self, state, control, target_update=False, transition_overwrite
258261
"""
259262
# Get current state vars
260263
r, theta, crs, spd = state
264+
265+
spd = random.randint(0,1)
266+
261267
control_spd = control[1]
262268

263269
theta = theta % 360
@@ -276,24 +282,12 @@ def update_state(self, state, control, target_update=False, transition_overwrite
276282
x, y = pol2cart(r, np.radians(theta))
277283

278284
# Generate next course given current course
279-
if target_update:
280-
spd = random.choice(self.target_speed_range)
281-
if self.target_movement == 'circular':
282-
d_crs, circ_spd = self.circular_control(50)
283-
crs += d_crs
284-
spd = circ_spd
285-
else:
286-
if random.random() >= self.prob_target_change_crs:
287-
crs += random.choice([-1, 1]) * 30
288-
else:
289-
if random.random() >= self.prob_target_change_crs:
290-
crs += random.choice([-1, 1]) * 30
285+
if random.random() >= self.prob_target_change_crs:
286+
crs += random.choice([-1, 1]) * 30
291287
crs %= 360
292288
if crs < 0:
293289
crs += 360
294290

295-
spd = random.randint(0,1)
296-
297291
# Transform changes to coords to cartesian
298292
dx, dy = pol2cart(spd, np.radians(crs))
299293
if transition_overwrite:
@@ -308,8 +302,81 @@ def update_state(self, state, control, target_update=False, transition_overwrite
308302

309303
return [r, theta, crs, spd]
310304

305+
# returns new state given last state and action (control)
306+
def update_real_state(self, state, distance=None, course=None, heading=None, **kwargs):
307+
"""Update state based on state and action
308+
309+
Parameters
310+
----------
311+
state_vars : list
312+
List of current state variables
313+
control : action (tuple)
314+
Action tuple
315+
316+
Returns
317+
-------
318+
State (array_like)
319+
Updated state values array
320+
"""
321+
if distance is None:
322+
distance = 0
323+
if course is None:
324+
course = 0
325+
if heading is None:
326+
heading = self.sensor_state[2]
327+
328+
# Get current state vars
329+
r, theta_deg, crs, spd = state
330+
if random.random() >= self.prob_target_change_crs:
331+
crs += random.choice([-1, 1]) * 30
332+
spd = random.randint(0,1)
333+
control_spd = distance
334+
control_course = course % 360
335+
control_delta_heading = (heading - self.sensor_state[2]) % 360
336+
337+
# polar -> cartesian
338+
x, y = pol2cart(r, np.radians(theta_deg))
339+
340+
# translate sensor movement
341+
dx, dy = pol2cart(control_spd, np.radians(control_course))
342+
pos = [x - dx, y - dy]
343+
344+
# translate target movement
345+
dx, dy = pol2cart(spd, np.radians(crs))
346+
pos = [pos[0] + dx, pos[1] + dy]
347+
348+
# cartesian -> polar
349+
r, theta = cart2pol(pos[0], pos[1])
350+
theta_deg = np.degrees(theta)
351+
352+
# rotation
353+
theta_deg -= control_delta_heading
354+
theta_deg %= 360
355+
crs -= control_delta_heading
356+
crs %= 360
357+
358+
return [r, theta_deg, crs, spd]
359+
360+
def update_real_sensor(self, distance, course, heading):
361+
362+
r, theta_deg, prev_heading, spd = self.sensor_state
363+
heading = heading if heading else prev_heading
364+
365+
if distance and course:
366+
spd = distance
367+
crs = course % 360
368+
dx, dy = pol2cart(spd, np.radians(crs))
369+
x, y = pol2cart(r, np.radians(theta_deg))
370+
pos = [x + dx, y + dy]
371+
372+
r = np.sqrt(pos[0]**2 + pos[1]**2)
373+
theta_deg = np.degrees(np.arctan2(pos[1], pos[0]))
374+
theta_deg %= 360
375+
376+
self.sensor_state = np.array([r, theta_deg, heading, spd])
377+
311378
def update_sensor(self, control, bearing=None):
312-
r, theta_deg, crs, spd = self.sensor_state
379+
r, theta_deg, crs, old_spd = self.sensor_state
313380

314381
spd = control[1]
315382

sigscan.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'previous_position': None,
3333
'bearing': None,
3434
'previous_bearing': None,
35+
'course': None,
3536
'action_proposal': None,
3637
'action_taken': None,
3738
'reward': None,
@@ -40,19 +41,22 @@
4041
# Generic data processor
4142
def data_handler(message_data):
4243
global data
43-
data['previous_position'] = data.get('position', None)
44-
data['previous_bearing'] = data.get('bearing', None)
44+
data['previous_position'] = data.get('position', None) if data.get('set_previous', False) else data.get('previous_position', None)
45+
data['previous_bearing'] = data.get('bearing', None) if data.get('set_previous', False) else data.get('previous_bearing', None)
4546

4647
data['rssi'] = message_data.get('rssi', None)
4748
data['position'] = message_data.get('position', None)
48-
data['bearing'] = -float(message_data.get('bearing', None))+90 if is_float(message_data.get('bearing', None)) else get_bearing(data['previous_position'], data['position'])
49+
data['course'] = get_bearing(data['previous_position'], data['position'])
50+
data['bearing'] = -float(message_data.get('bearing', None))+90 if is_float(message_data.get('bearing', None)) else data['course']
4951
data['distance'] = get_distance(data['previous_position'], data['position'])
5052
delta_bearing = (data['bearing'] - data['previous_bearing']) if data['bearing'] and data['previous_bearing'] else None
5153
data['action_taken'] = (delta_bearing, data['distance']) if delta_bearing and data['distance'] else (0,0)
5254

5355
data['drone_position'] = message_data.get('drone_position', None)
5456
if data['drone_position']:
55-
data['drone_position'] = [data['drone_position'][1], data['drone_position'][0]] # swap lon,lat
57+
data['drone_position'] = [data['drone_position'][1], data['drone_position'][0]] # swap lon,lat
58+
59+
data['set_previous'] = False
5660

5761
# MQTT
5862
def on_message(client, userdata, json_message):
@@ -204,6 +208,7 @@ def main(config=None, debug=False):
204208
# Flask
205209
fig = plt.figure(figsize=(18,10), dpi=50)
206210
ax = fig.subplots()
211+
fig.set_tight_layout(True)
207212
time_step = 0
208213
if config.get('flask', 'false').lower() == 'true':
209214
run_flask(flask_host, flask_port, fig, results, debug)
@@ -226,7 +231,8 @@ def main(config=None, debug=False):
226231

227232
step_start = timer()
228233
# update belief based on action and sensor observation (sensor is read inside)
229-
belief, reward, observation = env.real_step(data['action_taken'] if data.get('action_taken', None) else (0,0), data.get('bearing', None))
234+
belief, reward, observation = env.real_step(data)
235+
data['set_previous'] = True
230236
step_end = timer()
231237

232238
plot_start = timer()

0 commit comments

Comments
 (0)