Skip to content

Commit 9c526d8

Browse files
authored
Fix crossing frames wrongly counts movement endling on line (#376)
* Exclude movement ending on line counted as crossed * Add unit tests for crossing lines * Add a new internal function which allows different counting settings. These adaptions are needed, as the passing speed and density computations work differently, they count the crossing frames already when a pedestrian is touching the line. These function call the internal helper function, and compute_crossing_frames calls the helper function with a specific setting.
1 parent daba718 commit 9c526d8

3 files changed

Lines changed: 175 additions & 11 deletions

File tree

pedpy/methods/method_utils.py

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections import defaultdict
77
from dataclasses import dataclass
88
from enum import Enum, auto
9-
from typing import List, Optional, Tuple
9+
from typing import Final, List, Optional, Tuple
1010

1111
import numpy as np
1212
import pandas as pd
@@ -175,11 +175,13 @@ def compute_frame_range_in_area(
175175
traj_data=traj_data, measurement_area=measurement_area
176176
)
177177

178-
crossing_frames_first = compute_crossing_frames(
179-
traj_data=traj_data, measurement_line=measurement_line
178+
crossing_frames_first = _compute_crossing_frames(
179+
traj_data=traj_data,
180+
measurement_line=measurement_line,
181+
count_on_line=True,
180182
)
181-
crossing_frames_second = compute_crossing_frames(
182-
traj_data=traj_data, measurement_line=second_line
183+
crossing_frames_second = _compute_crossing_frames(
184+
traj_data=traj_data, measurement_line=second_line, count_on_line=True
183185
)
184186

185187
start_crossed_1 = _check_crossing_in_frame_range(
@@ -522,7 +524,9 @@ def compute_intersecting_polygons(
522524

523525

524526
def compute_crossing_frames(
525-
*, traj_data: TrajectoryData, measurement_line: MeasurementLine
527+
*,
528+
traj_data: TrajectoryData,
529+
measurement_line: MeasurementLine,
526530
) -> pd.DataFrame:
527531
"""Compute the frames at the pedestrians pass the measurement line.
528532
@@ -545,6 +549,35 @@ def compute_crossing_frames(
545549
traj_data (pandas.DataFrame): trajectory data
546550
measurement_line (MeasurementLine): measurement line which is crossed
547551
552+
Returns:
553+
DataFrame containing the columns 'id', 'frame', where 'frame' is
554+
the frame where the measurement line is crossed.
555+
"""
556+
return _compute_crossing_frames(
557+
traj_data=traj_data,
558+
measurement_line=measurement_line,
559+
count_on_line=False,
560+
)
561+
562+
563+
def _compute_crossing_frames(
564+
*,
565+
traj_data: TrajectoryData,
566+
measurement_line: MeasurementLine,
567+
count_on_line: bool,
568+
) -> pd.DataFrame:
569+
"""Compute the frames at which pedestrians pass the measurement line.
570+
571+
If count_on_line is set to True, the crossing frame is the one where the
572+
pedestrian touches the line. Otherwise, it is the frame where the
573+
pedestrian crosses the line without stopping on it.
574+
575+
Args:
576+
traj_data (pandas.DataFrame): trajectory data
577+
measurement_line (MeasurementLine): measurement line which is crossed
578+
count_on_line (bool): Count movement ending on line (True) or only if
579+
movement crosses line, but does not end on line.
580+
548581
Returns:
549582
DataFrame containing the columns 'id', 'frame', where 'frame' is
550583
the frame where the measurement line is crossed.
@@ -567,11 +600,29 @@ def compute_crossing_frames(
567600
)
568601
)
569602

570-
# crossing means, the current movement crosses the line and the end point
571-
# of the movement is not on the line. The result is sorted by frame number
572-
crossing_frames = df_movement.loc[
573-
shapely.intersects(df_movement.movement, measurement_line.line)
574-
][[ID_COL, FRAME_COL]]
603+
movement_crosses_line = shapely.intersects(
604+
df_movement.movement, measurement_line.line
605+
)
606+
607+
if count_on_line:
608+
crossing_frames = df_movement.loc[movement_crosses_line][
609+
[ID_COL, FRAME_COL]
610+
]
611+
else:
612+
# Case when crossing means movement crosses the line, but the end point
613+
# is not on it
614+
615+
# Minimum distance to consider crossing complete
616+
CROSSING_THRESHOLD: Final = 1e-5 # noqa: N806
617+
618+
movement_ends_on_line = (
619+
shapely.distance(df_movement.end_position, measurement_line.line)
620+
< CROSSING_THRESHOLD
621+
)
622+
623+
crossing_frames = df_movement.loc[
624+
(movement_crosses_line) & (~movement_ends_on_line)
625+
][[ID_COL, FRAME_COL]]
575626

576627
return crossing_frames
577628

tests/reference_tests/reference_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,16 @@ def test_nt(line, folder):
527527
traj_data=trajectory,
528528
measurement_line=line,
529529
)
530+
531+
# In JPSreport crossing was counted when a pedestrians touches the
532+
# measurement line the first time. In PedPy the crossing is counted
533+
# when the movement crossed the line and does not end on it. Hence,
534+
# some slight modifications in edge need to be done to adapt the
535+
# reference results.
536+
if folder.name == "corridor":
537+
reference_result.loc[243, CUMULATED_COL] -= 1
538+
reference_result.loc[3082, CUMULATED_COL] -= 1
539+
530540
assert (reference_result.index.values == result.index.values).all()
531541
assert np.isclose(
532542
result[TIME_COL], reference_result[TIME_COL], atol=TOLERANCE
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import numpy as np
2+
import pandas as pd
3+
import pytest
4+
import shapely
5+
6+
from pedpy.data.geometry import MeasurementLine
7+
from pedpy.data.trajectory_data import TrajectoryData
8+
from pedpy.methods.method_utils import compute_crossing_frames
9+
10+
11+
def create_single_straight_trajectory_crossing_zero_line(
12+
*, start_x=-10, frames_before, frames_on_line=0, end_x=10, frames_after
13+
):
14+
x = np.concatenate(
15+
[
16+
np.linspace(
17+
start_x, -0.1 if frames_on_line == 0 else 0, frames_before
18+
),
19+
np.linspace(0, 0, frames_on_line - 1 if frames_on_line > 0 else 0),
20+
np.linspace(0.1 if frames_on_line == 0 else 0, end_x, frames_after),
21+
]
22+
)
23+
y = np.zeros_like(x)
24+
25+
frames = np.arange(0, len(x))
26+
id = np.ones_like(x)
27+
28+
df_traj_simple = pd.DataFrame({"id": id, "frame": frames, "x": x, "y": y})
29+
return TrajectoryData(data=df_traj_simple, frame_rate=1)
30+
31+
32+
@pytest.mark.parametrize(
33+
"frames_before, frames_after",
34+
[
35+
(10, 5),
36+
(1, 10),
37+
],
38+
)
39+
def test_compute_crossing_frame_movement_across_line(
40+
frames_before, frames_after
41+
):
42+
traj = create_single_straight_trajectory_crossing_zero_line(
43+
frames_before=frames_before, frames_after=frames_after
44+
)
45+
ml = MeasurementLine([(0, -0.5), (0, 0.5)])
46+
crossing_frames = compute_crossing_frames(
47+
traj_data=traj, measurement_line=ml
48+
)
49+
50+
assert len(crossing_frames) == 1
51+
assert crossing_frames.iloc[0].id == 1
52+
assert crossing_frames.iloc[0].frame == frames_before
53+
54+
55+
@pytest.mark.parametrize(
56+
"frames_before, frames_on_line",
57+
[
58+
(3, 1),
59+
(10, 5),
60+
(7, 10),
61+
(0, 1),
62+
(0, 10),
63+
],
64+
)
65+
def test_compute_crossing_frame_movement_stops_on_line(
66+
frames_before, frames_on_line
67+
):
68+
traj = create_single_straight_trajectory_crossing_zero_line(
69+
frames_before=frames_before,
70+
frames_after=5,
71+
frames_on_line=frames_on_line,
72+
)
73+
ml = MeasurementLine([(0, -0.5), (0, 0.5)])
74+
75+
crossing_frames = compute_crossing_frames(
76+
traj_data=traj, measurement_line=ml
77+
)
78+
79+
assert len(crossing_frames) == 1
80+
assert crossing_frames.iloc[0].id == 1
81+
assert crossing_frames.iloc[0].frame == frames_before + frames_on_line
82+
83+
84+
@pytest.mark.parametrize(
85+
"frames_on_line",
86+
[
87+
1,
88+
5,
89+
10,
90+
],
91+
)
92+
def test_compute_crossing_frame_trajectory_ends_on_line(frames_on_line):
93+
traj = create_single_straight_trajectory_crossing_zero_line(
94+
frames_before=10,
95+
frames_after=0,
96+
frames_on_line=frames_on_line,
97+
)
98+
ml = MeasurementLine([(0, -0.5), (0, 0.5)])
99+
crossing_frames = compute_crossing_frames(
100+
traj_data=traj, measurement_line=ml
101+
)
102+
103+
assert len(crossing_frames) == 0

0 commit comments

Comments
 (0)