Skip to content

Commit

Permalink
Add function to compute distances between neighbors
Browse files Browse the repository at this point in the history
  • Loading branch information
schroedtert committed Feb 23, 2025
1 parent 919aaf8 commit 6f86a73
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 2 deletions.
32 changes: 31 additions & 1 deletion notebooks/user_guide.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -3176,7 +3176,14 @@
"We define two pedestrians as neighbors if their Voronoi polygons ($V_i$, $V_j$) touch at some point, in case of *PedPy* they are touching if their distance is below 1mm.\n",
"As basis for the computation one can either use the uncut or cut Voronoi polygons.\n",
"When using the uncut Voronoi polygons, pedestrian may be detected as neighbors even when their distance is quite large in low density situation.\n",
"Therefor, it is recommended to use the cut Voronoi polygons, where the cut-off radius can be used to define a maximal distance between neighboring pedestrians.\n",
"Therefor, it is recommended to use the cut Voronoi polygons, where the cut-off radius can be used to define a maximal distance between neighboring pedestrians."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Neighborhood computation\n",
"\n",
"To compute the neighbors in *PedPy* use:"
]
Expand Down Expand Up @@ -3250,6 +3257,29 @@
"neighbors_as_list[0:5]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Distance of neighbors\n",
"\n",
"For computing the distance between neighbors, *PedPy* offers a dedicated function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pedpy import compute_neighbor_distance\n",
"\n",
"neighbor_distance = compute_neighbor_distance(\n",
" traj_data=traj, neighborhood=neighbors\n",
")\n",
"neighbor_distance[0:5]"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
2 changes: 2 additions & 0 deletions pedpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
compute_frame_range_in_area,
compute_individual_voronoi_polygons,
compute_intersecting_polygons,
compute_neighbor_distance,
compute_neighbors,
compute_time_distance_line,
get_invalid_trajectory,
Expand Down Expand Up @@ -155,6 +156,7 @@
"compute_individual_voronoi_polygons",
"compute_intersecting_polygons",
"compute_neighbors",
"compute_neighbor_distance",
"compute_time_distance_line",
"get_invalid_trajectory",
"is_individual_speed_valid",
Expand Down
53 changes: 53 additions & 0 deletions pedpy/methods/method_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,59 @@ def _compute_neighbors_single(
)


def compute_neighbor_distance(
*,
traj_data: TrajectoryData,
neighborhood: pd.DataFrame,
) -> pd.DataFrame:
"""Compute the distance between the neighbors.
Computes the distance between the position of neighbors. As neighbors the
result of :func:`~compute_neighbors` with parameter :code:`as_list=False`.
Args:
traj_data (TrajectoryData): trajectory data
neighborhood (pd.DataFrame): DataFrame containing the columns 'id',
'frame' and 'neighbor_id'. The result of :func:`~compute_neighbors`
with parameter :code:`as_list=False` can be used here as input.
Raises:
ValueError: When passing a result of :func:`~compute_neighbors`
with parameter :code:`as_list=True`.
Returns:
DataFrame containing the columns 'id', 'frame', 'neighbor_id' and
'distance'.
"""
if NEIGHBORS_COL in neighborhood.columns:
raise ValueError(
"For compute the distance between neighbors compute the "
"neighborhood with `as_list=False`."
)

neighbors_with_position = neighborhood.merge(
traj_data.data[[ID_COL, FRAME_COL, POINT_COL]],
on=[ID_COL, FRAME_COL],
how="left",
)

neighbors_with_position = neighbors_with_position.merge(
traj_data.data[[ID_COL, FRAME_COL, POINT_COL]],
left_on=[NEIGHBOR_ID_COL, FRAME_COL],
right_on=[ID_COL, FRAME_COL],
suffixes=("", "_neighbor"),
)

neighbors_with_position[DISTANCE_COL] = shapely.distance(
neighbors_with_position[POINT_COL],
neighbors_with_position["point_neighbor"],
)

return neighbors_with_position[
[ID_COL, FRAME_COL, NEIGHBOR_ID_COL, DISTANCE_COL]
]


def compute_time_distance_line(
*, traj_data: TrajectoryData, measurement_line: MeasurementLine
) -> pd.DataFrame:
Expand Down
123 changes: 122 additions & 1 deletion tests/unit_tests/methods/test_method_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from unittest.mock import MagicMock

import numpy as np
import pandas as pd
import pytest
from shapely import Polygon
from shapely import Point, Polygon

from pedpy.column_identifier import *
from pedpy.data.geometry import MeasurementLine
Expand All @@ -11,6 +13,7 @@
_compute_orthogonal_speed_in_relation_to_proportion,
_compute_partial_line_length,
compute_crossing_frames,
compute_neighbor_distance,
compute_neighbors,
is_individual_speed_valid,
is_species_valid,
Expand Down Expand Up @@ -430,3 +433,121 @@ def test_compute_neighbors_no_touching():
)

pd.testing.assert_frame_equal(result_list, expected_list)


def test_compute_neighbor_distance():
traj_data = TrajectoryData(
data=pd.DataFrame(
{
ID_COL: [1, 2, 3],
FRAME_COL: [0, 0, 0],
X_COL: [0, 3, 6],
Y_COL: [0, 4, 8],
}
),
frame_rate=1,
)

neighborhood = pd.DataFrame(
{
ID_COL: [1, 2],
FRAME_COL: [0, 0],
NEIGHBOR_ID_COL: [2, 3],
}
)

result = compute_neighbor_distance(
traj_data=traj_data, neighborhood=neighborhood
)

expected_result = pd.DataFrame(
{
ID_COL: [1, 2],
FRAME_COL: [0, 0],
NEIGHBOR_ID_COL: [2, 3],
DISTANCE_COL: [
5.0,
5.0,
], # Euclidean distances: sqrt(3^2 + 4^2) = 5
}
)

pd.testing.assert_frame_equal(result, expected_result, check_dtype=False)


def test_compute_neighbor_distance_invalid_list_input():
traj_data = MagicMock(spec=TrajectoryData)
neighborhood = pd.DataFrame(
{
ID_COL: [1, 2],
FRAME_COL: [0, 0],
NEIGHBORS_COL: [[2], [3]], # as_list=True adds this column
}
)

with pytest.raises(
ValueError,
match="For compute the distance between neighbors compute the neighborhood with `as_list=False`.",
):
compute_neighbor_distance(
traj_data=traj_data, neighborhood=neighborhood
)


def test_compute_neighbor_distance_empty_input():
traj_data = TrajectoryData(
data=pd.DataFrame(columns=[ID_COL, FRAME_COL, X_COL, Y_COL]).astype(
{X_COL: "float64", Y_COL: "float64"}
),
frame_rate=1,
)
neighborhood = pd.DataFrame(columns=[ID_COL, FRAME_COL, NEIGHBOR_ID_COL])

result = compute_neighbor_distance(
traj_data=traj_data, neighborhood=neighborhood
)

expected_result = pd.DataFrame(
columns=[ID_COL, FRAME_COL, NEIGHBOR_ID_COL, DISTANCE_COL]
)

pd.testing.assert_frame_equal(result, expected_result, check_dtype=False)


def test_compute_neighbor_distance_different_distances():
traj_data = TrajectoryData(
data=pd.DataFrame(
{
ID_COL: [1, 2, 3],
FRAME_COL: [0, 0, 0],
X_COL: [0, 3, 10],
Y_COL: [0, 4, 10],
}
),
frame_rate=1,
)

neighborhood = pd.DataFrame(
{
ID_COL: [1, 2],
FRAME_COL: [0, 0],
NEIGHBOR_ID_COL: [2, 3],
}
)

result = compute_neighbor_distance(
traj_data=traj_data, neighborhood=neighborhood
)

expected_result = pd.DataFrame(
{
ID_COL: [1, 2],
FRAME_COL: [0, 0],
NEIGHBOR_ID_COL: [2, 3],
DISTANCE_COL: [5.0, 9.21954445729], # sqrt(7^2 + 6^2) = 9.21
}
)

pd.testing.assert_frame_equal(
result, expected_result, check_dtype=False, atol=1e-6
)

0 comments on commit 6f86a73

Please # to comment.