Skip to content

Commit 9851e31

Browse files
authored
Merge pull request #3 from SchwartzCode/update_master
Update master
2 parents e6f5dfe + 30a61ad commit 9851e31

File tree

60 files changed

+2458
-124
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2458
-124
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
1-
# Contributing to Python
1+
# Contributing
22

3-
:+1::tada: First of off, thanks very much for taking the time to contribute! :tada::+1:
3+
:+1::tada: First of all, thank you very much for taking the time to contribute! :tada::+1:
44

5-
The following is a set of guidelines for contributing to PythonRobotics.
6-
7-
These are mostly guidelines, not rules.
8-
9-
Use your best judgment, and feel free to propose changes to this document in a pull request.
10-
11-
# Before contributing
12-
13-
## Taking a look at the paper.
14-
15-
Please check this paper to understand the philosophy of this project.
16-
17-
- [\[1808\.10703\] PythonRobotics: a Python code collection of robotics algorithms](https://arxiv.org/abs/1808.10703) ([BibTeX](https://github.com/AtsushiSakai/PythonRoboticsPaper/blob/master/python_robotics.bib))
18-
19-
## Check your Python version.
20-
21-
We only accept a PR for Python 3.8.x or higher.
22-
23-
We will not accept a PR for Python 2.x.
5+
Please check this document for contribution: [How to contribute — PythonRobotics documentation](https://atsushisakai.github.io/PythonRobotics/modules/0_getting_started/3_how_to_contribute.html)
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
"""
2+
Distance Map
3+
4+
author: Wang Zheng (@Aglargil)
5+
6+
Ref:
7+
8+
- [Distance Map]
9+
(https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf)
10+
"""
11+
12+
import numpy as np
13+
import matplotlib.pyplot as plt
14+
import scipy
15+
16+
INF = 1e20
17+
ENABLE_PLOT = True
18+
19+
20+
def compute_sdf_scipy(obstacles):
21+
"""
22+
Compute the signed distance field (SDF) from a boolean field using scipy.
23+
This function has the same functionality as compute_sdf.
24+
However, by using scipy.ndimage.distance_transform_edt, it can compute much faster.
25+
26+
Example: 500×500 map
27+
• compute_sdf: 3 sec
28+
• compute_sdf_scipy: 0.05 sec
29+
30+
Parameters
31+
----------
32+
obstacles : array_like
33+
A 2D boolean array where '1' represents obstacles and '0' represents free space.
34+
35+
Returns
36+
-------
37+
array_like
38+
A 2D array representing the signed distance field, where positive values indicate distance
39+
to the nearest obstacle, and negative values indicate distance to the nearest free space.
40+
"""
41+
# distance_transform_edt use '0' as obstacles, so we need to convert the obstacles to '0'
42+
a = scipy.ndimage.distance_transform_edt(obstacles == 0)
43+
b = scipy.ndimage.distance_transform_edt(obstacles == 1)
44+
return a - b
45+
46+
47+
def compute_udf_scipy(obstacles):
48+
"""
49+
Compute the unsigned distance field (UDF) from a boolean field using scipy.
50+
This function has the same functionality as compute_udf.
51+
However, by using scipy.ndimage.distance_transform_edt, it can compute much faster.
52+
53+
Example: 500×500 map
54+
• compute_udf: 1.5 sec
55+
• compute_udf_scipy: 0.02 sec
56+
57+
Parameters
58+
----------
59+
obstacles : array_like
60+
A 2D boolean array where '1' represents obstacles and '0' represents free space.
61+
62+
Returns
63+
-------
64+
array_like
65+
A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`.
66+
"""
67+
return scipy.ndimage.distance_transform_edt(obstacles == 0)
68+
69+
70+
def compute_sdf(obstacles):
71+
"""
72+
Compute the signed distance field (SDF) from a boolean field.
73+
74+
Parameters
75+
----------
76+
obstacles : array_like
77+
A 2D boolean array where '1' represents obstacles and '0' represents free space.
78+
79+
Returns
80+
-------
81+
array_like
82+
A 2D array representing the signed distance field, where positive values indicate distance
83+
to the nearest obstacle, and negative values indicate distance to the nearest free space.
84+
"""
85+
a = compute_udf(obstacles)
86+
b = compute_udf(obstacles == 0)
87+
return a - b
88+
89+
90+
def compute_udf(obstacles):
91+
"""
92+
Compute the unsigned distance field (UDF) from a boolean field.
93+
94+
Parameters
95+
----------
96+
obstacles : array_like
97+
A 2D boolean array where '1' represents obstacles and '0' represents free space.
98+
99+
Returns
100+
-------
101+
array_like
102+
A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`.
103+
"""
104+
edt = obstacles.copy()
105+
if not np.all(np.isin(edt, [0, 1])):
106+
raise ValueError("Input array should only contain 0 and 1")
107+
edt = np.where(edt == 0, INF, edt)
108+
edt = np.where(edt == 1, 0, edt)
109+
for row in range(len(edt)):
110+
dt(edt[row])
111+
edt = edt.T
112+
for row in range(len(edt)):
113+
dt(edt[row])
114+
edt = edt.T
115+
return np.sqrt(edt)
116+
117+
118+
def dt(d):
119+
"""
120+
Compute 1D distance transform under the squared Euclidean distance
121+
122+
Parameters
123+
----------
124+
d : array_like
125+
Input array containing the distances.
126+
127+
Returns:
128+
--------
129+
d : array_like
130+
The transformed array with computed distances.
131+
"""
132+
v = np.zeros(len(d) + 1)
133+
z = np.zeros(len(d) + 1)
134+
k = 0
135+
v[0] = 0
136+
z[0] = -INF
137+
z[1] = INF
138+
for q in range(1, len(d)):
139+
s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k])
140+
while s <= z[k]:
141+
k = k - 1
142+
s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k])
143+
k = k + 1
144+
v[k] = q
145+
z[k] = s
146+
z[k + 1] = INF
147+
k = 0
148+
for q in range(len(d)):
149+
while z[k + 1] < q:
150+
k = k + 1
151+
dx = q - v[k]
152+
d[q] = dx * dx + d[int(v[k])]
153+
154+
155+
def main():
156+
obstacles = np.array(
157+
[
158+
[1, 0, 0, 0, 0],
159+
[0, 1, 1, 1, 0],
160+
[0, 1, 1, 1, 0],
161+
[0, 0, 1, 1, 0],
162+
[0, 0, 1, 0, 0],
163+
[0, 0, 0, 0, 0],
164+
[0, 0, 0, 0, 0],
165+
[0, 0, 0, 0, 0],
166+
[0, 0, 1, 0, 0],
167+
[0, 0, 0, 0, 0],
168+
[0, 0, 0, 0, 0],
169+
]
170+
)
171+
172+
# Compute the signed distance field
173+
sdf = compute_sdf(obstacles)
174+
udf = compute_udf(obstacles)
175+
176+
if ENABLE_PLOT:
177+
_, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
178+
179+
obstacles_plot = ax1.imshow(obstacles, cmap="binary")
180+
ax1.set_title("Obstacles")
181+
ax1.set_xlabel("x")
182+
ax1.set_ylabel("y")
183+
plt.colorbar(obstacles_plot, ax=ax1)
184+
185+
udf_plot = ax2.imshow(udf, cmap="viridis")
186+
ax2.set_title("Unsigned Distance Field")
187+
ax2.set_xlabel("x")
188+
ax2.set_ylabel("y")
189+
plt.colorbar(udf_plot, ax=ax2)
190+
191+
sdf_plot = ax3.imshow(sdf, cmap="RdBu")
192+
ax3.set_title("Signed Distance Field")
193+
ax3.set_xlabel("x")
194+
ax3.set_ylabel("y")
195+
plt.colorbar(sdf_plot, ax=ax3)
196+
197+
plt.tight_layout()
198+
plt.show()
199+
200+
201+
if __name__ == "__main__":
202+
main()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
A case study of robot behavior using state machine
3+
4+
author: Wang Zheng (@Aglargil)
5+
"""
6+
7+
from state_machine import StateMachine
8+
9+
10+
class Robot:
11+
def __init__(self):
12+
self.battery = 100
13+
self.task_progress = 0
14+
15+
# Initialize state machine
16+
self.machine = StateMachine("robot_sm", self)
17+
18+
# Add state transition rules
19+
self.machine.add_transition(
20+
src_state="patrolling",
21+
event="detect_task",
22+
dst_state="executing_task",
23+
guard=None,
24+
action=None,
25+
)
26+
27+
self.machine.add_transition(
28+
src_state="executing_task",
29+
event="task_complete",
30+
dst_state="patrolling",
31+
guard=None,
32+
action="reset_task",
33+
)
34+
35+
self.machine.add_transition(
36+
src_state="executing_task",
37+
event="low_battery",
38+
dst_state="returning_to_base",
39+
guard="is_battery_low",
40+
)
41+
42+
self.machine.add_transition(
43+
src_state="returning_to_base",
44+
event="reach_base",
45+
dst_state="charging",
46+
guard=None,
47+
action=None,
48+
)
49+
50+
self.machine.add_transition(
51+
src_state="charging",
52+
event="charge_complete",
53+
dst_state="patrolling",
54+
guard=None,
55+
action="battery_full",
56+
)
57+
58+
# Set initial state
59+
self.machine.set_current_state("patrolling")
60+
61+
def is_battery_low(self):
62+
"""Battery level check condition"""
63+
return self.battery < 30
64+
65+
def reset_task(self):
66+
"""Reset task progress"""
67+
self.task_progress = 0
68+
print("[Action] Task progress has been reset")
69+
70+
# Modify state entry callback naming convention (add state_ prefix)
71+
def on_enter_executing_task(self):
72+
print("\n------ Start Executing Task ------")
73+
print(f"Current battery: {self.battery}%")
74+
while self.machine.get_current_state().name == "executing_task":
75+
self.task_progress += 10
76+
self.battery -= 25
77+
print(
78+
f"Task progress: {self.task_progress}%, Remaining battery: {self.battery}%"
79+
)
80+
81+
if self.task_progress >= 100:
82+
self.machine.process("task_complete")
83+
break
84+
elif self.is_battery_low():
85+
self.machine.process("low_battery")
86+
break
87+
88+
def on_enter_returning_to_base(self):
89+
print("\nLow battery, returning to charging station...")
90+
self.machine.process("reach_base")
91+
92+
def on_enter_charging(self):
93+
print("\n------ Charging ------")
94+
self.battery = 100
95+
print("Charging complete!")
96+
self.machine.process("charge_complete")
97+
98+
99+
# Keep the test section structure the same, only modify the trigger method
100+
if __name__ == "__main__":
101+
robot = Robot()
102+
print(robot.machine.generate_plantuml())
103+
104+
print(f"Initial state: {robot.machine.get_current_state().name}")
105+
print("------------")
106+
107+
# Trigger task detection event
108+
robot.machine.process("detect_task")
109+
110+
print("\n------------")
111+
print(f"Final state: {robot.machine.get_current_state().name}")

0 commit comments

Comments
 (0)