Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pair distribution function #320

Merged
merged 29 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
29535b5
udate-version-file
ThoChat Mar 21, 2024
8c3fea1
First implementation pdf computation
ThoChat Mar 21, 2024
7e278ad
First debuged version
ThoChat Mar 22, 2024
ba86377
2d and optimisation
ThoChat Mar 27, 2024
d82530d
Add notebook to test pdf function
chraibi Mar 29, 2024
241f123
Add documentation and vectorised version of dist calculation
chraibi Mar 29, 2024
40a63dd
fix stack of arrays and use Divide
chraibi Mar 30, 2024
ace9e02
Raise warning for division by null values during pdf computation #330
ThoChat Apr 3, 2024
b596877
Merge branch 'PedestrianDynamics:main' into Pair-Distribution-Function
ThoChat Apr 3, 2024
b103e1b
Add a parameter to pdf to control the number of data scrambling loops…
ThoChat Apr 4, 2024
44601d2
Merge remote-tracking branch 'origin/Pair-Distribution-Function' into…
ThoChat Apr 4, 2024
82cfe43
Method error fix
ThoChat Apr 4, 2024
6d5c74b
Change atribute name and documentation
ThoChat Apr 4, 2024
795479c
First modification of the User guide
ThoChat Apr 16, 2024
94138eb
Final Changes User guide and remove test codes
ThoChat Apr 17, 2024
f8c60cf
Minor correction User guide
ThoChat Apr 17, 2024
5616020
del _version for the PR
ThoChat Apr 17, 2024
73c2ee8
Clear User Guide output
ThoChat Apr 22, 2024
15b71c1
Bug fix
ThoChat Apr 22, 2024
aeba30c
Changing name of the file containning the pdf computation
ThoChat Apr 23, 2024
a92690c
Minor correction and formating to pass all continuity tests
ThoChat Apr 29, 2024
a1ca8fd
formating
ThoChat Apr 29, 2024
9163e47
formatting
ThoChat Apr 29, 2024
5ae398a
formating
ThoChat Apr 29, 2024
da9f51b
formating
ThoChat Apr 29, 2024
7bc59cf
Editing the text of PDF
chraibi May 28, 2024
f742424
fix small typos
chraibi May 28, 2024
91901aa
Changing name in the API doc
ThoChat Jun 3, 2024
6e7475c
formating
ThoChat Jun 3, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,4 @@ cython_debug/
docs/build

# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,pycharm,jupyternotebooks
.vscode/launch.json
1,102 changes: 1,102 additions & 0 deletions notebooks/demo-data/single_file/n34_cam2.csv

Large diffs are not rendered by default.

2,392 changes: 2,392 additions & 0 deletions notebooks/demo-data/single_file/n56_cam1.csv

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions notebooks/test_pdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os
import pathlib

import matplotlib.pyplot as plt
import pandas as pd

import pedpy


# Set path to data directory
path = "./notebooks/demo-data/single_file/"

# Read trajectory files
# files = os.listdir(path)

# Choose data file
file = "n56_cam1.csv"

### Read data and create DataFrame
df = pd.read_csv(os.path.join(path, file), comment="#")
df = df.rename(columns={'ID': 'id', 'Frame': 'frame'})[["id","frame","x","y"]]
df["frame"]
traj = pedpy.TrajectoryData(df,25)

# traj=pedpy.load_trajectory_from_ped_data_archive_hdf5(pathlib.Path(path+"00_01a.h5"))




radius_bins, pair_distibution = pedpy.compute_pair_distibution_function(
traj_data=traj, radius_bin_size=0.1
)

fig, (ax1) = plt.subplots(1, figsize=(5, 5))

# Plot g(r) on the first subplot
ax1.plot(radius_bins, pair_distibution)
plt.show()
4 changes: 4 additions & 0 deletions pedpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
get_invalid_trajectory,
is_trajectory_valid,
)
from .methods.other_characterisation_methods import (
compute_pair_distibution_function,
)
from .methods.profile_calculator import (
DensityMethod,
SpeedMethod,
Expand Down Expand Up @@ -104,6 +107,7 @@
"compute_time_distance_line",
"get_invalid_trajectory",
"is_trajectory_valid",
"compute_pair_distibution_function",
"DensityMethod",
"SpeedMethod",
"compute_density_profile",
Expand Down
3 changes: 3 additions & 0 deletions pedpy/_version.py
chraibi marked this conversation as resolved.
Show resolved Hide resolved
chraibi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = "1.2.0.dev2"
__commit_hash__ = "8c3fea1"

108 changes: 108 additions & 0 deletions pedpy/methods/other_characterisation_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Module containing functions to compute velocities."""
from typing import Optional, Tuple

import numpy as np
import numpy.typing as npt
import pandas

from pedpy.column_identifier import FRAME_COL, ID_COL,X_COL,Y_COL,V_X_COL,V_Y_COL,POINT_COL
from pedpy.data.geometry import MeasurementArea
from pedpy.data.trajectory_data import TrajectoryData
from pedpy.methods.speed_calculator import (
SpeedCalculation,
compute_individual_speed)


def compute_pair_distibution_function(
*,
traj_data: TrajectoryData,
radius_bin_size: float
):

df =traj_data.data

# Create Dataframe with all mutual distances, TTCs, and Rates of Approach
dist_pd_array = calculate_data_frame_pair_dist(df)

# Scramble time-information to mitigate finite-size effects and calculate mutual distances of scrambled dataset
df.frame = df.frame.sample(frac=1).reset_index(drop=True)
dist_pd_ni_array = calculate_data_frame_pair_dist(df)

## Create the bin for data
radius_bins = np.arange(0, dist_pd_array.max(), radius_bin_size)

# Calculate pair distibution: g(r)
Nb_dist=len(dist_pd_array)
## Actual distribution
pd_bins=pandas.cut(dist_pd_array,radius_bins)
pd_bins_normalised = (pd_bins.value_counts().sort_index().to_numpy())/Nb_dist
## Scrambled distribution
pd_ni_bins=pandas.cut(dist_pd_ni_array,radius_bins)
pd_ni_bins_normalised = (pd_ni_bins.value_counts().sort_index().to_numpy())/Nb_dist

pair_distibution = pd_bins_normalised/pd_ni_bins_normalised

return radius_bins[1:], pair_distibution


# Function to calculate Dataframe of pairwise distances
def calculate_data_frame_pair_dist(df):
gdf = df.groupby(FRAME_COL)

# Compute the number of possible combinaison of id per frame
N_val = 0
for _, df_f in gdf:
ids = len(df_f[ID_COL].unique()) # Number of unique ids
N_val += int(ids * (ids - 1) / 2) # Calculate combinations of ids

dist_array = np.empty((N_val)) # Initialize array to store results
index = 0
for _, df_f in gdf: # Iterate over groups (frames)
gdf_f_id = df_f.groupby(ID_COL)
chraibi marked this conversation as resolved.
Show resolved Hide resolved
for i, (_, df_i) in enumerate(gdf_f_id):
for _, df_j in list(gdf_f_id)[i + 1 :]:
# Populate array with pairwise distances
dist_array[index] =(df_j[POINT_COL].iloc[0]).distance(df_i[POINT_COL].iloc[0])
index += 1
return dist_array





# # Function to initialize observables for a pair of data frames
# def init_observables(observables, df_i, df_j, index):

# observables[index, 1] = ttc(
# df_i[X_COL].iloc[0],
# df_j[X_COL].iloc[0],
# df_i[V_X_COL].iloc[0],
# df_j[V_X_COL].iloc[0],
# ) # Calculate time-to-collision
# observables[index, 2] = rate_of_approach(
# df_i[X_COL].iloc[0],
# df_j[X_COL].iloc[0],
# df_i[V_X_COL].iloc[0],
# df_j[V_X_COL].iloc[0],
# ) # Calculate rate of approach


## Function to calculate time-to-collision
# def ttc(x1, x2, v1, v2, l_a=0.2, l_b=0.2):
# if (v1 - v2) != 0:
# ttc = ((x2 - x1) - (0.5 * (l_a + l_b))) / (v1 - v2)
# if ttc > 0:
# return ttc # Formula for time-to-collision
# else:
# return 99999.9 # Return a large value if conditions are not met
# else:
# return 99999.9 # Return a large value if conditions are not met



# # Function to calculate rate of approach
# def rate_of_approach(x_a, x_b, v_a, v_b):
# return -1 * (v_a - v_b) * (x_a - x_b) / d(x_a, x_b)



142 changes: 142 additions & 0 deletions pedpy/methods/pair_distribution_function_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""Module containing functions to compute velocities."""
from typing import Optional, Tuple

import numpy as np
import numpy.typing as npt
import pandas

from pedpy.column_identifier import FRAME_COL, ID_COL,X_COL,Y_COL,V_X_COL,V_Y_COL
from pedpy.data.geometry import MeasurementArea
from pedpy.data.trajectory_data import TrajectoryData
from pedpy.methods.speed_calculator import (
SpeedCalculation,
compute_individual_speed)


def compute_pair_distibution_function(
chraibi marked this conversation as resolved.
Show resolved Hide resolved
*,
traj_data: TrajectoryData,
radius_max: float,
dx: float,
movement_direction: Optional[npt.NDArray[np.float64]] = None,
speed_calculation: SpeedCalculation = SpeedCalculation.BORDER_EXCLUDE,
):
speed = compute_individual_speed(
traj_data=traj_data,
frame_step=5,
compute_velocity=True,
)

df = pandas.merge(traj_data.data, speed, on=[ID_COL, FRAME_COL])[
[ID_COL, FRAME_COL, X_COL,Y_COL,V_X_COL,V_Y_COL]
]
# Create Dataframe with all mutual distances, TTCs, and Rates of Approach
df_pd = calculate_data_frame_pair_dist(df)

# Scramble time-information to mitigate finite-size effects and calculate mutual distances of scrambled dataset
df.frame = df.frame.sample(frac=1).reset_index(drop=True)
df_pd_ni = calculate_data_frame_pair_dist(df)

radius_bins = np.arange(0, radius_max, dx)

# Calculate pair distibution: g(r)
pair_distibution = calc_pair_dist(df_pd.r, df_pd_ni.r, radius_bins, dx)

return radius_bins, pair_distibution


# Function to calculate Dataframe of pairwise distances
def calculate_data_frame_pair_dist(df):
gdf = df.groupby(FRAME_COL)

# Compute the number of possible combinaison of id per frame
N_val = 0
for _, df_f in gdf:
ids = len(df_f[ID_COL].unique()) # Number of unique ids
N_val += int(ids * (ids - 1) / 2) # Calculate combinations of ids

observables = np.empty((N_val, 3)) # Initialize array to store results
index = 0
for _, df_f in gdf: # Iterate over groups (frames)
gdf_f_id = df_f.groupby(ID_COL)
for i, (_, df_i) in enumerate(gdf_f_id):
for _, df_j in list(gdf_f_id)[i + 1 :]:
init_observables(
observables, df_i, df_j, index
) # Populate array with pairwise distances
index += 1
return pandas.DataFrame(
{
"r": observables[:, 0],
"ttc": observables[:, 1],
"roa": observables[:, 2],
}
)


# Function to initialize observables for a pair of data frames
def init_observables(observables, df_i, df_j, index):
observables[index, 0] = d(
df_i[X_COL].iloc[0], df_j[X_COL].iloc[0]
) # Calculate distance
observables[index, 1] = ttc(
df_i[X_COL].iloc[0],
df_j[X_COL].iloc[0],
df_i[V_X_COL].iloc[0],
df_j[V_X_COL].iloc[0],
) # Calculate time-to-collision
observables[index, 2] = rate_of_approach(
df_i[X_COL].iloc[0],
df_j[X_COL].iloc[0],
df_i[V_X_COL].iloc[0],
df_j[V_X_COL].iloc[0],
) # Calculate rate of approach


# Function to calculate time-to-collision
def ttc(x1, x2, v1, v2, l_a=0.2, l_b=0.2):
if (v1 - v2) != 0:
ttc = ((x2 - x1) - (0.5 * (l_a + l_b))) / (v1 - v2)
if ttc > 0:
return ttc # Formula for time-to-collision
else:
return 99999.9 # Return a large value if conditions are not met
else:
return 99999.9 # Return a large value if conditions are not met


# Function to calculate absolute difference
def d(a, b):
return abs(a - b)


# Function to calculate rate of approach
def rate_of_approach(x_a, x_b, v_a, v_b):
return -1 * (v_a - v_b) * (x_a - x_b) / d(x_a, x_b)


# Function to calculate pairwise distances
def calc_pair_dist(x, x_scrambled, x_bins, dx):
x1 = calc_dist(x, x_bins, dx)
x2 = calc_dist(x_scrambled, x_bins, dx)
# x2 = np.ones((len(x1)))*(1/len(x1)) # Equi-distribution
### division by 0 typicaly 0/0 are set to == 0
# with np.errstate(divide='ignore', invalid='ignore'):
# c = np.divide(x1,x2)
# c[c == np.nan] = 0
# c = np.nan_to_num(c)
c = x1 / x2
return c


# Function to calculate distances
def calc_dist(x, x_bins, dx):
x_min = min(x_bins)
x_max = max(x_bins)
p_x = np.zeros(len(x_bins)) # Initialize array to store counts
for x_i in x:
if x_min < x_i < x_max + dx:
p_x[
int(np.floor((x_i - x_min) / dx))
] += 1 # Increment count for corresponding bin
return p_x / len(x) # Calculate probability distribution
Loading