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

Contact folder #2

Merged
merged 14 commits into from
Mar 22, 2024
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ __pycache__/
.venv
AppData
*.pdf
*.pkl
*.sh
*.zip
*.zip
*._*.txt
*._*.csv
./data/trajectories/
4 changes: 4 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from src.helpers.log_config import setup_logging
from src.tabs.analysis_tab import run_tab3
from src.tabs.contacts_tab import run_tab_contact
from src.tabs.animation_tab import run_tab_animation
from src.tabs.explorer import run_explorer
from src.tabs.map_tab import run_tab_map
from src.tabs.traj_tab import run_tab2
Expand Down Expand Up @@ -40,3 +41,6 @@

if selected_tab == "Explorer":
run_explorer()

if selected_tab == "Animation":
run_tab_animation()
Binary file not shown.
77 changes: 77 additions & 0 deletions src/tabs/animation_tab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Streamlit app to create an animation of pedestrian movements."""

from pathlib import Path
import pandas as pd
import streamlit as st
import plotly.express as px
from plotly.graph_objs import Figure


def load_data(pickle_name: str) -> pd.DataFrame:
"""
Load pedestrian trajectory data from a pickle file.

Args:
pickle_name (str): The name of the pickle file to load.

Returns:
pd.DataFrame: DataFrame containing the pedestrian trajectory data.
"""
pd_trajs = pd.read_pickle(pickle_name)
return pd_trajs


def create_animation(pd_trajs: pd.DataFrame) -> Figure:
"""
Create a Plotly animation of pedestrian movements.

Args:
pd_trajs (pd.DataFrame): DataFrame containing the pedestrian trajectory data.

Returns:
Figure: Plotly figure object with the pedestrian movement animation.
"""
# Create the scatter_mapbox figure
fig = px.scatter_mapbox(
pd_trajs,
lat="lat_wgs84", # Ensure this column contains latitude values
lon="lon_wgs84", # Ensure this column contains longitude values
hover_name="id",
animation_frame="frame",
animation_group="id",
color="id",
color_discrete_sequence=["fuchsia"],
zoom=17.5,
mapbox_style="open-street-map",
)

# Adjust animation speed
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1
fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = 1

# Adjust layout size if needed
fig.update_layout(height=700, width=900)

return fig


def main() -> None:
"""
Main function to run the Streamlit app.
"""
path = Path(__file__)
PICKLE_NAME = str(path.parent.parent.parent.absolute() / "data" / "contacts" / "dataframe_trajectories_oscar_WGS84.pkl")

pd_trajs = load_data(PICKLE_NAME)
# Drop columns 'x' and 'y' if they exist to reduce the size
pd_trajs.drop(columns=["x", "y"], inplace=True, errors='ignore')


st.title("Animation Pedestrian on place des Terreaux")
fig = create_animation(pd_trajs)
st.plotly_chart(fig, use_container_width=True)


def run_tab_animation() -> None:
main()

67 changes: 41 additions & 26 deletions src/tabs/contacts_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

import folium
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from matplotlib import colormaps
Expand Down Expand Up @@ -44,8 +45,7 @@ def initialize_map(all_gps_tracks: pd.DataFrame) -> folium.Map:
Returns:
folium.Map: A folium map object.
"""
first_track_df = all_gps_tracks[all_gps_tracks["name_subj"] == "Ludovic-Gardre1"]
map_center = first_track_df.iloc[len(first_track_df) // 2][["latitude", "longitude"]].tolist()
map_center = [45.76714745916146, 4.833552178368124] # first_track_df.iloc[len(first_track_df) // 2][["latitude", "longitude"]].tolist()
return folium.Map(location=map_center, zoom_start=17.5)


Expand Down Expand Up @@ -82,7 +82,7 @@ def plot_gps_tracks(map_object: folium.Map, all_gps_tracks: pd.DataFrame) -> Non
track_points = track_df[["latitude", "longitude"]].values.tolist()
rgba_color = viridis(track_index / len(unique_tracks))
hex_color = mcolors.to_hex(rgba_color)
folium.PolyLine(track_points, color=hex_color, weight=2.5, opacity=1, popup=name_subj).add_to(map_object)
folium.PolyLine(track_points, color=hex_color, weight=2.5, opacity=1).add_to(map_object)


def add_contact_markers(map_object: folium.Map, contact_gps_merged: pd.DataFrame, path_icon: str) -> None:
Expand All @@ -102,7 +102,7 @@ def add_contact_markers(map_object: folium.Map, contact_gps_merged: pd.DataFrame
).add_to(map_object)


def plot_histogram(df: pd.DataFrame, bins: int) -> Figure:
def plot_histogram(df: pd.DataFrame, bins: int, log_plot: Tuple[bool,bool]) -> Figure:
"""
Creates an interactive bar chart using Plotly to visualize the total number of collisions.

Expand All @@ -112,40 +112,40 @@ def plot_histogram(df: pd.DataFrame, bins: int) -> Figure:
Returns:
Figure: The Plotly figure object for the histogram.
"""
fig = px.histogram(
df,
x="Total-number-of-collisions",
nbins=bins,
marginal="rug",
hover_data=df.columns,
labels={"waiting": "Waiting time"},
text_auto=True,
title="<b>Histogram of the total number of collisions</b>",
)
fig.update_layout(bargap=0.2, xaxis_title="Number of contacts along the path", yaxis_title="Number of people") # Set the range for the log scale

fig, ax = plt.subplots(figsize=(2, 2), dpi=10)
sns.histplot(df['Total-number-of-collisions'], bins=bins, kde=True, log_scale=(log_plot[0], log_plot[1]), ax=ax)
plt.xlabel('Number of contacts along the path')
plt.ylabel('Number of people')
plt.title('Histogram of the total number of collisions')
plt.savefig(Path(__file__).parent.parent.parent.absolute() / "data" / "processed" / f"histogram_{bins}.pdf")


return fig


def plot_cumulative_contacts(df: pd.DataFrame) -> Figure:
"""To plot cumulative contacts as a function of time using Plotly"""
# Initialize an empty figure
fig = go.Figure()
# Drop the non-numeric 'Détail' columns
detail_data = df.drop(columns=["Name", "Date", "Time-of-stop", "Total-number-of-collisions", "Duration"], inplace=False)

fig = go.Figure()
# Loop through the DataFrame and plot each person's contact times
for index, row in df.iterrows():
for index, row in detail_data.iterrows():
times = row.dropna().values # Get the 'Détail' times for the person
if len(times) > 0:
values = np.cumsum(np.concatenate(([0], np.ones(len(times), dtype="int")))) # type: ignore
edges = np.concatenate((times, [df["Duration"].iloc[index].total_seconds()]))
# Add a trace for each person
fig.add_trace(go.Scatter(x=edges, y=values, mode="lines+markers", name=row["Name"]))
fig.add_trace(go.Scatter(x=edges, y=values, mode="lines+markers"))

# Update layout of the figure
fig.update_layout(
title="Cumulative Contacts as a Function of Time",
xaxis_title="Time [microseconds]",
xaxis_title="Time [seconds]",
yaxis_title="Cumulative Number of Contacts",
drawstyle="steps-pre",
width=600, height=600
)

return fig
Expand All @@ -172,18 +172,33 @@ def main() -> None:

# Display the map in the Streamlit app
st_folium(my_map, width=825, height=700)

# Slider for selecting the number of bins
plt = st.empty()
bins = int(st.slider("Select number of bins:", min_value=5, max_value=11, value=10, step=3))
fig = plot_histogram(contacts_data, bins)
plt.plotly_chart(fig, use_container_width=True)
bins = int(st.slider("Select number of bins:", min_value=5, max_value=11, value=6, step=1))

# Initialize the session state variable if it doesn't exist
if 'bool_var' not in st.session_state:
st.session_state['bool_var'] = True

# Create a button in the Streamlit app
if st.button('log-x-scale'):
# When the button is clicked, toggle the session state boolean variable
st.session_state['bool_var'] = not st.session_state['bool_var']

# Display the current value of the session state boolean variable
st.write(f'Current value of boolean variable: {st.session_state["bool_var"]}')

fig = plot_histogram(contacts_data, bins, (st.session_state["bool_var"],False))
figname = Path(f"histogram_{bins}.pdf")
path = Path(__file__)
data_directory = path.parent.parent.parent.absolute() / "data" / "processed"
figname = data_directory / Path(figname)
fig.write_image(figname)
st.pyplot(fig)
download_file(figname)


fig = plot_cumulative_contacts(contacts_data)
st.plotly_chart(fig)

def run_tab_contact() -> None:
main()
27 changes: 23 additions & 4 deletions src/tabs/map_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Camera:
"Open Street Map": "openstreetmap",
"CartoDB Positron": "CartoDB positron",
"CartoDB Dark_Matter": "CartoDB dark_matter",
"Google Satellite": "google_satellite"
}


Expand Down Expand Up @@ -77,7 +78,7 @@ def load_cameras_from_json(file_path: str) -> Dict[str, Camera]:

def create_map(
center: List[float],
tile_layer: str,
selected_layer: str,
cameras: Dict[str, Camera],
zoom: int = 16,
) -> folium.Map:
Expand All @@ -92,8 +93,21 @@ def create_map(
"""
path = Path(__file__).parent.parent.parent
logo_cameras = path / "data" / "assets" / "logo_cameras"
m = folium.Map(location=center, zoom_start=zoom, tiles=tile_layer, max_zoom=21)

m = folium.Map(location=center, zoom_start=zoom, max_zoom=21)
if selected_layer == "Google Satellite":
google_satellite = folium.TileLayer(
tiles="http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}",
attr="Google",
name="Google Satellite",
overlay=True,
control=True,
opacity=1.0,
)
google_satellite.add_to(m)
else:
# Assuming 'tile_layers' is a dictionary that maps layer names to their tile URLs
folium.TileLayer(tile_layers[selected_layer], attr="Attribution for the tile source").add_to(m)

camera_layers = []
for name in cameras.keys():
camera_layers.append(
Expand Down Expand Up @@ -172,7 +186,12 @@ def main(cameras: Dict[str, Camera], selected_layer: str) -> None:
cameras (Dict[str, Camera]): A dictionary of Camera objects.
"""
center = [45.76322690683106, 4.83001470565796] # Coordinates for Lyon, France
m = create_map(center, tile_layer=tile_layers[selected_layer], cameras=cameras)

m = create_map(center, selected_layer=selected_layer, cameras=cameras)




c1, c2 = st.columns((0.8, 0.2))
with c1:
map_data = st_folium(m, width=800, height=700)
Expand Down
3 changes: 2 additions & 1 deletion src/ui/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ def init_sidebar() -> Any:
"""
return option_menu(
"Multi-agent modelling of dense crowd dynamics: Predict & Understand",
["About", "Map", "Trajectories", "Analysis", "Contacts", "Explorer"],
["About", "Map", "Trajectories", "Analysis", "Contacts", "Explorer", "Animation"],
icons=[
"info-square",
"pin-map",
"people",
"bar-chart-line",
"exclamation-triangle",
"graph-up-arrow",
"camera-reels-fill"
],
menu_icon="cast",
default_index=0,
Expand Down
Loading