Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
70269fe
update getting started (#1138)
AtsushiSakai Feb 3, 2025
5b06435
build(deps): bump ruff from 0.9.3 to 0.9.4 in /requirements (#1139)
dependabot[bot] Feb 3, 2025
7b7bd78
update introduction (#1141)
AtsushiSakai Feb 5, 2025
322fead
feat: add DistanceMap (#1142)
Aglargil Feb 5, 2025
2234abf
fix: DistanceMap doc autofunction (#1143)
Aglargil Feb 6, 2025
0676dfd
update introduction (#1144)
AtsushiSakai Feb 6, 2025
9936f34
update introduction (#1145)
AtsushiSakai Feb 7, 2025
15e1068
Update CONTRIBUTING.md
AtsushiSakai Feb 7, 2025
a8f3388
update introduction (#1146)
AtsushiSakai Feb 9, 2025
e304f07
update introduction (#1147)
AtsushiSakai Feb 10, 2025
610f35f
build(deps): bump mypy from 1.14.1 to 1.15.0 in /requirements (#1148)
dependabot[bot] Feb 11, 2025
ba30767
build(deps): bump ruff from 0.9.4 to 0.9.6 in /requirements (#1149)
dependabot[bot] Feb 11, 2025
b298609
update introduction doc (#1151)
AtsushiSakai Feb 11, 2025
be608f0
update introduction doc (#1152)
AtsushiSakai Feb 12, 2025
1ecc154
update contribution link in README.md to fix invalid link (#1154)
AtsushiSakai Feb 13, 2025
1564830
update robotics definition document to enhance references and clarity…
AtsushiSakai Feb 13, 2025
77ad334
update robotics definition document to improve clarity and add refere…
AtsushiSakai Feb 14, 2025
35c0882
add external sensors documentation to appendix (#1159)
AtsushiSakai Feb 15, 2025
e82a123
add internal sensors documentation to appendix and create new interna…
AtsushiSakai Feb 16, 2025
c92aaf3
feat: add ElasticBands (#1156)
Aglargil Feb 17, 2025
cbe61f8
build(deps): bump scipy from 1.15.1 to 1.15.2 in /requirements (#1163)
dependabot[bot] Feb 18, 2025
395fca5
fix: update robotics documentation for clarity and correct terminolog…
AtsushiSakai Feb 18, 2025
d537119
fix: update robotics documentation for clarity and correct terminolog…
AtsushiSakai Feb 18, 2025
8064488
build(deps): bump numpy from 2.2.2 to 2.2.3 in /requirements (#1164)
dependabot[bot] Feb 18, 2025
2b70809
Add GitHub copilot pro sponser (#1167)
AtsushiSakai Feb 19, 2025
c7fb228
fix: update section references to use consistent formatting (#1169)
AtsushiSakai Feb 20, 2025
f343573
Update move_to_pose for cases where alpha > pi/2 or alpha < -pi/2 (#1…
Aglargil Feb 20, 2025
6477929
refactor: rename files and update references for inverted pendulum an…
AtsushiSakai Feb 21, 2025
6e13e82
build(deps): bump ruff from 0.9.6 to 0.9.7 in /requirements (#1173)
dependabot[bot] Feb 24, 2025
0c8ff11
Space-Time AStar (#1170)
SchwartzCode Feb 25, 2025
67a3ca7
add state machine (#1172)
Aglargil Feb 28, 2025
346037a
build(deps): bump pytest from 8.3.4 to 8.3.5 in /requirements (#1178)
dependabot[bot] Mar 3, 2025
cd09abd
build(deps): bump ruff from 0.9.7 to 0.9.9 in /requirements (#1179)
dependabot[bot] Mar 4, 2025
5f3be9b
build(deps): bump matplotlib from 3.10.0 to 3.10.1 in /requirements (…
dependabot[bot] Mar 4, 2025
30a61ad
bug: fix typo on line 6 of SpaceTimeAStar.py (#1182)
spnsingh Mar 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 3 additions & 21 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# Contributing to Python
# Contributing

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

The following is a set of guidelines for contributing to PythonRobotics.

These are mostly guidelines, not rules.

Use your best judgment, and feel free to propose changes to this document in a pull request.

# Before contributing

## Taking a look at the paper.

Please check this paper to understand the philosophy of this project.

- [\[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))

## Check your Python version.

We only accept a PR for Python 3.8.x or higher.

We will not accept a PR for Python 2.x.
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)
202 changes: 202 additions & 0 deletions Mapping/DistanceMap/distance_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""
Distance Map

author: Wang Zheng (@Aglargil)

Ref:

- [Distance Map]
(https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf)
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy

INF = 1e20
ENABLE_PLOT = True


def compute_sdf_scipy(obstacles):
"""
Compute the signed distance field (SDF) from a boolean field using scipy.
This function has the same functionality as compute_sdf.
However, by using scipy.ndimage.distance_transform_edt, it can compute much faster.

Example: 500×500 map
• compute_sdf: 3 sec
• compute_sdf_scipy: 0.05 sec

Parameters
----------
obstacles : array_like
A 2D boolean array where '1' represents obstacles and '0' represents free space.

Returns
-------
array_like
A 2D array representing the signed distance field, where positive values indicate distance
to the nearest obstacle, and negative values indicate distance to the nearest free space.
"""
# distance_transform_edt use '0' as obstacles, so we need to convert the obstacles to '0'
a = scipy.ndimage.distance_transform_edt(obstacles == 0)
b = scipy.ndimage.distance_transform_edt(obstacles == 1)
return a - b


def compute_udf_scipy(obstacles):
"""
Compute the unsigned distance field (UDF) from a boolean field using scipy.
This function has the same functionality as compute_udf.
However, by using scipy.ndimage.distance_transform_edt, it can compute much faster.

Example: 500×500 map
• compute_udf: 1.5 sec
• compute_udf_scipy: 0.02 sec

Parameters
----------
obstacles : array_like
A 2D boolean array where '1' represents obstacles and '0' represents free space.

Returns
-------
array_like
A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`.
"""
return scipy.ndimage.distance_transform_edt(obstacles == 0)


def compute_sdf(obstacles):
"""
Compute the signed distance field (SDF) from a boolean field.

Parameters
----------
obstacles : array_like
A 2D boolean array where '1' represents obstacles and '0' represents free space.

Returns
-------
array_like
A 2D array representing the signed distance field, where positive values indicate distance
to the nearest obstacle, and negative values indicate distance to the nearest free space.
"""
a = compute_udf(obstacles)
b = compute_udf(obstacles == 0)
return a - b


def compute_udf(obstacles):
"""
Compute the unsigned distance field (UDF) from a boolean field.

Parameters
----------
obstacles : array_like
A 2D boolean array where '1' represents obstacles and '0' represents free space.

Returns
-------
array_like
A 2D array of distances from the nearest obstacle, with the same dimensions as `bool_field`.
"""
edt = obstacles.copy()
if not np.all(np.isin(edt, [0, 1])):
raise ValueError("Input array should only contain 0 and 1")
edt = np.where(edt == 0, INF, edt)
edt = np.where(edt == 1, 0, edt)
for row in range(len(edt)):
dt(edt[row])
edt = edt.T
for row in range(len(edt)):
dt(edt[row])
edt = edt.T
return np.sqrt(edt)


def dt(d):
"""
Compute 1D distance transform under the squared Euclidean distance

Parameters
----------
d : array_like
Input array containing the distances.

Returns:
--------
d : array_like
The transformed array with computed distances.
"""
v = np.zeros(len(d) + 1)
z = np.zeros(len(d) + 1)
k = 0
v[0] = 0
z[0] = -INF
z[1] = INF
for q in range(1, len(d)):
s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k])
while s <= z[k]:
k = k - 1
s = ((d[q] + q * q) - (d[int(v[k])] + v[k] * v[k])) / (2 * q - 2 * v[k])
k = k + 1
v[k] = q
z[k] = s
z[k + 1] = INF
k = 0
for q in range(len(d)):
while z[k + 1] < q:
k = k + 1
dx = q - v[k]
d[q] = dx * dx + d[int(v[k])]


def main():
obstacles = np.array(
[
[1, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
]
)

# Compute the signed distance field
sdf = compute_sdf(obstacles)
udf = compute_udf(obstacles)

if ENABLE_PLOT:
_, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))

obstacles_plot = ax1.imshow(obstacles, cmap="binary")
ax1.set_title("Obstacles")
ax1.set_xlabel("x")
ax1.set_ylabel("y")
plt.colorbar(obstacles_plot, ax=ax1)

udf_plot = ax2.imshow(udf, cmap="viridis")
ax2.set_title("Unsigned Distance Field")
ax2.set_xlabel("x")
ax2.set_ylabel("y")
plt.colorbar(udf_plot, ax=ax2)

sdf_plot = ax3.imshow(sdf, cmap="RdBu")
ax3.set_title("Signed Distance Field")
ax3.set_xlabel("x")
ax3.set_ylabel("y")
plt.colorbar(sdf_plot, ax=ax3)

plt.tight_layout()
plt.show()


if __name__ == "__main__":
main()
111 changes: 111 additions & 0 deletions MissionPlanning/StateMachine/robot_behavior_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
A case study of robot behavior using state machine

author: Wang Zheng (@Aglargil)
"""

from state_machine import StateMachine


class Robot:
def __init__(self):
self.battery = 100
self.task_progress = 0

# Initialize state machine
self.machine = StateMachine("robot_sm", self)

# Add state transition rules
self.machine.add_transition(
src_state="patrolling",
event="detect_task",
dst_state="executing_task",
guard=None,
action=None,
)

self.machine.add_transition(
src_state="executing_task",
event="task_complete",
dst_state="patrolling",
guard=None,
action="reset_task",
)

self.machine.add_transition(
src_state="executing_task",
event="low_battery",
dst_state="returning_to_base",
guard="is_battery_low",
)

self.machine.add_transition(
src_state="returning_to_base",
event="reach_base",
dst_state="charging",
guard=None,
action=None,
)

self.machine.add_transition(
src_state="charging",
event="charge_complete",
dst_state="patrolling",
guard=None,
action="battery_full",
)

# Set initial state
self.machine.set_current_state("patrolling")

def is_battery_low(self):
"""Battery level check condition"""
return self.battery < 30

def reset_task(self):
"""Reset task progress"""
self.task_progress = 0
print("[Action] Task progress has been reset")

# Modify state entry callback naming convention (add state_ prefix)
def on_enter_executing_task(self):
print("\n------ Start Executing Task ------")
print(f"Current battery: {self.battery}%")
while self.machine.get_current_state().name == "executing_task":
self.task_progress += 10
self.battery -= 25
print(
f"Task progress: {self.task_progress}%, Remaining battery: {self.battery}%"
)

if self.task_progress >= 100:
self.machine.process("task_complete")
break
elif self.is_battery_low():
self.machine.process("low_battery")
break

def on_enter_returning_to_base(self):
print("\nLow battery, returning to charging station...")
self.machine.process("reach_base")

def on_enter_charging(self):
print("\n------ Charging ------")
self.battery = 100
print("Charging complete!")
self.machine.process("charge_complete")


# Keep the test section structure the same, only modify the trigger method
if __name__ == "__main__":
robot = Robot()
print(robot.machine.generate_plantuml())

print(f"Initial state: {robot.machine.get_current_state().name}")
print("------------")

# Trigger task detection event
robot.machine.process("detect_task")

print("\n------------")
print(f"Final state: {robot.machine.get_current_state().name}")
Loading