diff --git a/._logo.png b/._logo.png
deleted file mode 100644
index 5b29361..0000000
Binary files a/._logo.png and /dev/null differ
diff --git a/._my_map.html b/._my_map.html
deleted file mode 100644
index b09c1e6..0000000
Binary files a/._my_map.html and /dev/null differ
diff --git a/app.py b/app.py
index 3af799e..932ec41 100644
--- a/app.py
+++ b/app.py
@@ -2,38 +2,37 @@
import streamlit as st
-import analysis_tab
-import contacts_tab
-import datafactory
-import docs
-import map_tab
-import traj_tab
-import ui
-from log_config import setup_logging
+from src.classes.datafactory import init_session_state
+from src.docs import docs
+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.map_tab import run_tab_map
+from src.tabs.traj_tab import run_tab2
+from src.ui.ui import init_app_looks, init_sidebar, setup_app
setup_logging()
if __name__ == "__main__":
- ui.setup_app()
- selected = ui.init_sidebar()
- ui.init_app_looks()
- datafactory.init_session_state()
+ setup_app()
+ selected_tab = init_sidebar()
+ init_app_looks()
+ init_session_state()
- if selected == "About":
+ if selected_tab == "About":
docs.about()
- if selected == "Map":
- map_tab.call_main()
+ if selected_tab == "Map":
+ run_tab_map()
- if selected == "Trajectories":
+ if selected_tab == "Trajectories":
msg = st.empty()
- filename = str(
- st.selectbox(":open_file_folder: **Select a file**", st.session_state.files)
- )
- st.session_state.selected_file = filename
- traj_tab.run_tab2(filename, msg)
+ file_name_to_path = {path.split('/')[-1]: path for path in st.session_state.files}
+ filename = str(st.selectbox(":open_file_folder: **Select a file**", file_name_to_path))
+ st.session_state.selected_file = file_name_to_path[filename]
+ run_tab2(file_name_to_path[filename], msg)
- if selected == "Analysis":
- analysis_tab.run_tab3()
+ if selected_tab == "Analysis":
+ run_tab3()
- if selected == "Contacts":
- contacts_tab.call_main()
+ if selected_tab == "Contacts":
+ run_tab_contact()
diff --git a/data.zip b/data.zip
deleted file mode 100644
index 5379c41..0000000
Binary files a/data.zip and /dev/null differ
diff --git a/cameras.json b/data/assets/cameras.json
similarity index 83%
rename from cameras.json
rename to data/assets/cameras.json
index 25ff2ec..ea8f14e 100644
--- a/cameras.json
+++ b/data/assets/cameras.json
@@ -4,62 +4,62 @@
"field" : [[45.767808, 4.833684], [45.767708, 4.833739], [45.767719, 4.833813], [45.767808, 4.833684]],
"url": "https://youtu.be/EVgS2sGqrMk?si=nnApDBU_RpagUwbC",
"name": "restaurant l'Etage",
- "logo": "./logo_cameras/cam_letage.png"
+ "logo": "cam_letage.png"
},
"camera2": {
"location": [45.767807, 4.833659],
"field" : [[45.767807, 4.833659], [45.767708, 4.833739], [45.767700, 4.833664], [45.767807, 4.833659]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "airbnb terreaux 1",
- "logo": "./logo_cameras/cam_airbnb_terreaux1.png"
+ "logo": "cam_airbnb_terreaux1.png"
},
"camera3": {
"location": [45.767803, 4.833640],
"field" : [[45.767803, 4.833640], [45.767700, 4.833664], [45.767686, 4.833547], [45.767803, 4.833640]],
"url": "https://youtu.be/EVgS2sGqrMk?si=nnApDBU_RpagUwbC",
"name": "airbnb terreaux 2",
- "logo": "./logo_cameras/cam_airbnb_terreaux2.png"
+ "logo": "cam_airbnb_terreaux2.png"
},
"camera4": {
"location": [45.767562, 4.834459],
"field" : [[45.767562, 4.834459], [45.767090, 4.832775], [45.767700, 4.832677], [45.767562, 4.834459]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "bell tower of the city hall" ,
- "logo": "./logo_cameras/cam_bell_tower.png"
+ "logo": "cam_bell_tower.png"
},
"camera5": {
"location": [45.767353, 4.834343],
"field" : [[45.767353, 4.834343], [45.767275, 4.834433], [45.767372, 4.834200], [45.767353, 4.834343]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "city hall corner",
- "logo": "./logo_cameras/cam_city_hall_corner.png"
+ "logo": "cam_city_hall_corner.png"
},
"camera6": {
"location": [45.767026, 4.832412],
"field" : [[45.767026, 4.832412], [45.767124, 4.832372], [45.767136, 4.832460], [45.767026, 4.832412]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "airbnb street Constantine 1",
- "logo": "./logo_cameras/cam_airbnb_constantine1.png"
+ "logo": "cam_airbnb_constantine1.png"
},
"camera7": {
"location": [45.767021, 4.832376],
"field" : [[45.767021, 4.832376], [45.767124, 4.832372], [45.767115, 4.832296], [45.767021, 4.832376]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "airbnb street Constantine 2",
- "logo": "./logo_cameras/cam_airbnb_constantine2.png"
+ "logo": "cam_airbnb_constantine2.png"
},
"camera8": {
"location": [45.761136, 4.826233],
"field" : [[45.761136, 4.826233], [45.761089, 4.826463], [45.761231, 4.826482], [45.761136, 4.826233]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "Saint Jean 1",
- "logo": "./logo_cameras/cam_saint_jean1.png"
+ "logo": "cam_saint_jean1.png"
},
"camera9": {
"location": [45.761070, 4.826219],
"field" : [[45.761070, 4.826219], [45.761089, 4.826463], [45.760959, 4.826404], [45.761070, 4.826219]],
"url": "https://youtu.be/L__Rpl45gqU?si=s9OxC8m3pRiFpMQ6",
"name": "Saint Jean 2",
- "logo": "./logo_cameras/cam_saint_jean2.png"
+ "logo": "cam_saint_jean2.png"
}
}
diff --git a/logo.png b/data/assets/logo.png
similarity index 100%
rename from logo.png
rename to data/assets/logo.png
diff --git a/logo_cameras/cam_airbnb_constantine1.png b/data/assets/logo_cameras/cam_airbnb_constantine1.png
similarity index 100%
rename from logo_cameras/cam_airbnb_constantine1.png
rename to data/assets/logo_cameras/cam_airbnb_constantine1.png
diff --git a/logo_cameras/cam_airbnb_constantine2.png b/data/assets/logo_cameras/cam_airbnb_constantine2.png
similarity index 100%
rename from logo_cameras/cam_airbnb_constantine2.png
rename to data/assets/logo_cameras/cam_airbnb_constantine2.png
diff --git a/logo_cameras/cam_airbnb_terreaux1.png b/data/assets/logo_cameras/cam_airbnb_terreaux1.png
similarity index 100%
rename from logo_cameras/cam_airbnb_terreaux1.png
rename to data/assets/logo_cameras/cam_airbnb_terreaux1.png
diff --git a/logo_cameras/cam_airbnb_terreaux2.png b/data/assets/logo_cameras/cam_airbnb_terreaux2.png
similarity index 100%
rename from logo_cameras/cam_airbnb_terreaux2.png
rename to data/assets/logo_cameras/cam_airbnb_terreaux2.png
diff --git a/logo_cameras/cam_bell_tower.png b/data/assets/logo_cameras/cam_bell_tower.png
similarity index 100%
rename from logo_cameras/cam_bell_tower.png
rename to data/assets/logo_cameras/cam_bell_tower.png
diff --git a/logo_cameras/cam_city_hall_corner.png b/data/assets/logo_cameras/cam_city_hall_corner.png
similarity index 100%
rename from logo_cameras/cam_city_hall_corner.png
rename to data/assets/logo_cameras/cam_city_hall_corner.png
diff --git a/logo_cameras/cam_letage.png b/data/assets/logo_cameras/cam_letage.png
similarity index 100%
rename from logo_cameras/cam_letage.png
rename to data/assets/logo_cameras/cam_letage.png
diff --git a/logo_cameras/cam_saint_jean1.png b/data/assets/logo_cameras/cam_saint_jean1.png
similarity index 100%
rename from logo_cameras/cam_saint_jean1.png
rename to data/assets/logo_cameras/cam_saint_jean1.png
diff --git a/logo_cameras/cam_saint_jean2.png b/data/assets/logo_cameras/cam_saint_jean2.png
similarity index 100%
rename from logo_cameras/cam_saint_jean2.png
rename to data/assets/logo_cameras/cam_saint_jean2.png
diff --git a/logo_contact/contact_icon.png b/data/assets/logo_contact/contact_icon.png
similarity index 100%
rename from logo_contact/contact_icon.png
rename to data/assets/logo_contact/contact_icon.png
diff --git a/my_map.html b/data/assets/my_map.html
similarity index 100%
rename from my_map.html
rename to data/assets/my_map.html
diff --git a/App_data_contacts/all_gps_tracks.pickle b/data/contacts/all_gps_tracks.pickle
similarity index 100%
rename from App_data_contacts/all_gps_tracks.pickle
rename to data/contacts/all_gps_tracks.pickle
diff --git a/App_data_contacts/contacts_data.pickle b/data/contacts/contacts_data.pickle
similarity index 100%
rename from App_data_contacts/contacts_data.pickle
rename to data/contacts/contacts_data.pickle
diff --git a/App_data_contacts/contacts_gps_merged.pickle b/data/contacts/contacts_gps_merged.pickle
similarity index 100%
rename from App_data_contacts/contacts_gps_merged.pickle
rename to data/contacts/contacts_gps_merged.pickle
diff --git a/images/._fbppj-FestivalOfLights2-min.png b/data/images/._fbppj-FestivalOfLights2-min.png
similarity index 100%
rename from images/._fbppj-FestivalOfLights2-min.png
rename to data/images/._fbppj-FestivalOfLights2-min.png
diff --git a/images/._fcaym-FdL22.png b/data/images/._fcaym-FdL22.png
similarity index 100%
rename from images/._fcaym-FdL22.png
rename to data/images/._fcaym-FdL22.png
diff --git a/images/fbppj-FestivalOfLights2-min.png b/data/images/fbppj-FestivalOfLights2-min.png
similarity index 100%
rename from images/fbppj-FestivalOfLights2-min.png
rename to data/images/fbppj-FestivalOfLights2-min.png
diff --git a/images/fcaym-FdL22.png b/data/images/fcaym-FdL22.png
similarity index 100%
rename from images/fcaym-FdL22.png
rename to data/images/fcaym-FdL22.png
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/classes/__init__.py b/src/classes/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/datafactory.py b/src/classes/datafactory.py
similarity index 91%
rename from datafactory.py
rename to src/classes/datafactory.py
index 0e812da..a569b86 100644
--- a/datafactory.py
+++ b/src/classes/datafactory.py
@@ -1,6 +1,7 @@
"""Unsorted datastructure for the app."""
import glob
+import logging
import os
import shutil
import zipfile
@@ -89,8 +90,10 @@ def init_state_bg_image() -> None:
def init_session_state() -> None:
"""Init session_state throughout the app."""
+ path = Path(__file__)
+ trajectories_directory = path.parent.parent.parent.absolute() / "data" / "trajectories"
+ logging.info(f"{trajectories_directory = }")
init_state_bg_image()
-
# Initialize a list of DirectionInfo objects using the provided dictionaries
if "direction_infos" not in st.session_state:
st.session_state.direction_infos = [
@@ -115,7 +118,7 @@ def init_session_state() -> None:
if not hasattr(st.session_state, "trajectory_data"):
st.session_state.trajectoryData = pedpy.TrajectoryData
- dataconfig = DataConfig(Path("AppData"))
+ dataconfig = DataConfig(trajectories_directory)
st.session_state.files = dataconfig.files
@@ -134,21 +137,15 @@ def unzip_files(zip_path: Union[str, Path], destination: Union[str, Path]) -> No
# Extract only if file (ignores directories)
if not member.is_dir():
# Build target filename path
- target_path = os.path.join(
- destination, os.path.basename(member.filename)
- )
+ target_path = os.path.join(destination, os.path.basename(member.filename))
# Ensure target directory exists (e.g., if not extracting directories)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
# Extract file
- with zip_ref.open(member, "r") as source, open(
- target_path, "wb"
- ) as target:
+ with zip_ref.open(member, "r") as source, open(target_path, "wb") as target:
shutil.copyfileobj(source, target)
-def download_and_unzip_files(
- url: str, destination: Union[str, Path], unzip_destination: Union[str, Path]
-) -> None:
+def download_and_unzip_files(url: str, destination: Union[str, Path], unzip_destination: Union[str, Path]) -> None:
"""
Download a ZIP file from a specified URL.
diff --git a/src/docs/__init__.py b/src/docs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/docs.py b/src/docs/docs.py
similarity index 92%
rename from docs.py
rename to src/docs/docs.py
index 62377a8..ac9f254 100644
--- a/docs.py
+++ b/src/docs/docs.py
@@ -1,10 +1,11 @@
"""Documentation texts for the app."""
+from pathlib import Path
from typing import List
import streamlit as st
-from datafactory import Direction
+from ..classes.datafactory import Direction
def flow(directions: List[Direction]) -> None:
@@ -63,15 +64,18 @@ def density_speed() -> None:
def about() -> None:
"""Write About text."""
+ path = Path(__file__)
+ ROOT_DIR = path.parent.parent.parent.absolute()
+ img_path_1 = ROOT_DIR / "data" / "images" / "fcaym-FdL22.png"
+ img_path_2 = ROOT_DIR / "data" / "images" / "fbppj-FestivalOfLights2-min.png"
text = """
-
## Overview
The [MADRAS-project](https://www.madras-crowds.eu/) is a collaborative cooperation funded by [ANR](https://anr.fr) :flag-fr: and [DFG](htpps://dfg.de) :flag-de:, aims to develop innovative agent-based models to predict and understand dense crowd dynamics and to apply these models in a large-scale case study.
This app offers a visualisation of data collection of the festival of lights in 2022, a distinguished open-air event that draws nearly two million visitors over four days.
"""
st.markdown(text)
- st.image("images/fcaym-FdL22.png", caption="Festival of Lights in Lyon 2022.")
+ st.image(str(img_path_1), caption="Festival of Lights in Lyon 2022.")
text2 = """
This app is part of the MADRAS project, which focuses on collecting and analyzing videos of crowded scenes during the festival. The primary goal is to extract valuable pedestrian dynamics measurements to enhance our understanding of crowd behaviors during such large-scale events.
@@ -91,6 +95,6 @@ def about() -> None:
"""
st.markdown(text3)
st.image(
- "images/fbppj-FestivalOfLights2-min.png",
+ str(img_path_2),
caption="Emplacement of cameras for the video recording during the Festival of Lights 2022.",
)
diff --git a/src/helpers/__init__.py b/src/helpers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/log_config.py b/src/helpers/log_config.py
similarity index 100%
rename from log_config.py
rename to src/helpers/log_config.py
diff --git a/utilities.py b/src/helpers/utilities.py
similarity index 87%
rename from utilities.py
rename to src/helpers/utilities.py
index 52eb1e4..0f67692 100644
--- a/utilities.py
+++ b/src/helpers/utilities.py
@@ -10,7 +10,7 @@
import streamlit as st
from shapely import Polygon
-from datafactory import Direction, DirectionInfo
+from ..classes.datafactory import Direction, DirectionInfo
def is_running_locally() -> bool:
@@ -61,9 +61,7 @@ def get_id_by_name(direction_name: str) -> int:
return -1
-def get_measurement_lines(
- trajectory_data: pd.DataFrame, distance_to_bounding: float
-) -> List[Direction]:
+def get_measurement_lines(trajectory_data: pd.DataFrame, distance_to_bounding: float) -> List[Direction]:
"""Create 4 measurement lines inside the walkable_area."""
min_x = trajectory_data.data["x"].min() + distance_to_bounding
max_x = trajectory_data.data["x"].max() - distance_to_bounding
@@ -72,15 +70,11 @@ def get_measurement_lines(
return [
Direction(
- info=DirectionInfo(
- id=get_id_by_name("East"), name="East", color=get_color_by_name("East")
- ),
+ info=DirectionInfo(id=get_id_by_name("East"), name="East", color=get_color_by_name("East")),
line=pedpy.MeasurementLine([[min_x, min_y], [min_x, max_y]]),
),
Direction(
- info=DirectionInfo(
- id=get_id_by_name("West"), name="West", color=get_color_by_name("West")
- ),
+ info=DirectionInfo(id=get_id_by_name("West"), name="West", color=get_color_by_name("West")),
line=pedpy.MeasurementLine([[max_x, min_y], [max_x, max_y]]),
),
Direction(
@@ -118,9 +112,7 @@ def setup_walkable_area(trajectory_data: pd.DataFrame) -> pedpy.WalkableArea:
return pedpy.WalkableArea(rectangle_polygon)
-def setup_measurement_area(
- min_x: float, max_x: float, min_y: float, max_y: float
-) -> pedpy.MeasurementArea:
+def setup_measurement_area(min_x: float, max_x: float, min_y: float, max_y: float) -> pedpy.MeasurementArea:
"""Create measurement_area from trajectories."""
rectangle_coords = [
[min_x, min_y],
diff --git a/analysis.ipynb b/src/notebooks/analysis.ipynb
similarity index 100%
rename from analysis.ipynb
rename to src/notebooks/analysis.ipynb
diff --git a/profiles.ipynb b/src/notebooks/profiles.ipynb
similarity index 100%
rename from profiles.ipynb
rename to src/notebooks/profiles.ipynb
diff --git a/anim.py b/src/plotting/anim.py
similarity index 92%
rename from anim.py
rename to src/plotting/anim.py
index 3871ecb..9fa6e11 100644
--- a/anim.py
+++ b/src/plotting/anim.py
@@ -8,7 +8,6 @@
code here. Use it at your own peril.
"""
-
from typing import Any, Dict, List, Tuple
import matplotlib.pyplot as plt
@@ -21,7 +20,8 @@
from plotly.graph_objs.layout import Shape
from shapely import Polygon
-import datafactory
+from ..classes.datafactory import (decrement_frame_start,
+ increment_frame_start, reset_frame_start)
DUMMY_SPEED = -1000
@@ -40,9 +40,7 @@ def _get_line_color(disk_color: str) -> str:
return "black" if brightness > 127 else "white"
-def _create_orientation_line(
- row: pd.DataFrame, line_length: float = 0.2, color: str = "black"
-) -> Shape:
+def _create_orientation_line(row: pd.DataFrame, line_length: float = 0.2, color: str = "black") -> Shape:
"""Create orientation Shape object."""
end_x = row["x"] + line_length * 0
end_y = row["y"] + line_length * 0
@@ -113,9 +111,7 @@ def _get_colormap(frame_data: pd.DataFrame, max_speed: float) -> List[Scatter]:
return [scatter_trace]
-def _get_shapes_for_frame(
- frame_data: pd.DataFrame, min_speed: float, max_speed: float
-) -> Tuple[Shape, Scatter, Shape]:
+def _get_shapes_for_frame(frame_data: pd.DataFrame, min_speed: float, max_speed: float) -> Tuple[Shape, Scatter, Shape]:
"""Construct circles as Shapes for agents, Hover and Directions."""
def create_shape(row: pd.DataFrame) -> Shape:
@@ -210,9 +206,7 @@ def _create_fig(
fig = go.Figure(
data=geometry_traces + initial_scatter_trace + initial_hover_trace,
frames=frames,
- layout=go.Layout(
- shapes=initial_shapes + initial_arrows, title=title, title_x=0.5
- ),
+ layout=go.Layout(shapes=initial_shapes + initial_arrows, title=title, title_x=0.5),
)
fig.update_layout(
updatemenus=[_get_animation_controls()],
@@ -275,9 +269,7 @@ def _get_slider_controls(steps: List[Dict[str, Any]]) -> Dict[str, Any]:
}
-def _get_processed_frame_data(
- data_df: pd.DataFrame, frame_num: int, max_agents: int
-) -> Tuple[pd.DataFrame, int]:
+def _get_processed_frame_data(data_df: pd.DataFrame, frame_num: int, max_agents: int) -> Tuple[pd.DataFrame, int]:
"""Process frame data and ensure it matches the maximum agent count."""
frame_data = data_df[data_df["frame"] == frame_num]
agent_count = len(frame_data)
@@ -317,18 +309,18 @@ def animate(
p1.text("Backward")
decrement = st.button(":arrow_backward:")
if decrement:
- datafactory.decrement_frame_start(int(page_size))
+ decrement_frame_start(int(page_size))
with col2:
p2.text("Forward")
increment = st.button(":arrow_forward:")
if increment:
- datafactory.increment_frame_start(int(page_size))
+ increment_frame_start(int(page_size))
with col3:
p3.text("Reset")
reset = st.button(":leftwards_arrow_with_hook:")
if reset:
- datafactory.reset_frame_start(fr0)
+ reset_frame_start(fr0)
every_nth_frame = st.sidebar.number_input(
"fps",
@@ -350,9 +342,7 @@ def animate(
# Calculate page_end
frame_end = st.session_state.start_frame + page_size
frame_start = st.session_state.start_frame
- data_df = data_df0[
- (data_df0["frame"] >= frame_start) & (data_df0["frame"] <= frame_end)
- ]
+ data_df = data_df0[(data_df0["frame"] >= frame_start) & (data_df0["frame"] <= frame_end)]
min_speed = data_df["speed"].min()
max_speed = data_df["speed"].max()
@@ -371,12 +361,8 @@ def animate(
) = _get_shapes_for_frame(initial_frame_data, min_speed, max_speed)
color_map_trace = _get_colormap(initial_frame_data, max_speed)
for frame_num in selected_frames:
- frame_data, agent_count = _get_processed_frame_data(
- data_df, frame_num, max_agents
- )
- shapes, hover_traces, arrows = _get_shapes_for_frame(
- frame_data, min_speed, max_speed
- )
+ frame_data, agent_count = _get_processed_frame_data(data_df, frame_num, max_agents)
+ shapes, hover_traces, arrows = _get_shapes_for_frame(frame_data, min_speed, max_speed)
# title = f"{title_note + ' | ' if title_note else ''}N: {agent_count}"
title = f"{title_note + ' | ' if title_note else ''}Number of Agents: {initial_agent_count}. Frame: {frame_num}"
frame_name = str(int(frame_num))
diff --git a/drawing.py b/src/plotting/drawing.py
similarity index 82%
rename from drawing.py
rename to src/plotting/drawing.py
index 694c195..5619b86 100644
--- a/drawing.py
+++ b/src/plotting/drawing.py
@@ -7,13 +7,11 @@
import streamlit as st
from streamlit_drawable_canvas import st_canvas
-import plots
-import utilities
+from ..helpers.utilities import setup_measurement_area
+from .plots import draw_bg_img, draw_rects
-def drawing_canvas(
- trajectory_data: pedpy.TrajectoryData, walkable_area: pedpy.WalkableArea
-) -> Tuple[Any, float, float, float]:
+def drawing_canvas(trajectory_data: pedpy.TrajectoryData, walkable_area: pedpy.WalkableArea) -> Tuple[Any, float, float, float]:
"""Draw trajectories as img and prepare canvas."""
drawing_mode = st.sidebar.radio(
"**Measurement:**",
@@ -36,9 +34,7 @@ def drawing_canvas(
min_y = trajectory_data.data["y"].min()
max_y = trajectory_data.data["y"].max()
- bg_img, img_width, img_height, dpi, scale = plots.bg_img(
- data, min_x, max_x, min_y, max_y
- )
+ bg_img, img_width, img_height, dpi, scale = draw_bg_img(data, min_x, max_x, min_y, max_y)
st.session_state.scale = scale
st.session_state.dpi = dpi
st.session_state.img_width = img_width
@@ -80,7 +76,7 @@ def get_measurement_area(
max_y = trajectory_data.data["y"].max()
boundaries = (min_x, max_x, min_y, max_y)
measurement_areas = []
- rects = plots.draw_rects(
+ rects = draw_rects(
canvas,
img_height,
dpi,
@@ -88,9 +84,7 @@ def get_measurement_area(
boundaries,
)
if not rects:
- measurement_areas.append(
- utilities.setup_measurement_area(min_x, max_x, min_y, max_y)
- )
+ measurement_areas.append(setup_measurement_area(min_x, max_x, min_y, max_y))
return measurement_areas
for ir, _ in enumerate(rects):
@@ -98,8 +92,6 @@ def get_measurement_area(
to_x = rects[ir]["x"][1]
from_y = rects[ir]["y"][3]
to_y = rects[ir]["y"][0]
- measurement_areas.append(
- utilities.setup_measurement_area(from_x, to_x, from_y, to_y)
- )
+ measurement_areas.append(setup_measurement_area(from_x, to_x, from_y, to_y))
return measurement_areas
diff --git a/plots.py b/src/plotting/plots.py
similarity index 91%
rename from plots.py
rename to src/plotting/plots.py
index 6709b49..1632ca9 100644
--- a/plots.py
+++ b/src/plotting/plots.py
@@ -18,7 +18,7 @@
from plotly.graph_objs import Figure, Scatter
from plotly.subplots import make_subplots
-from utilities import get_color_by_name
+from ..helpers.utilities import get_color_by_name
st_column: TypeAlias = st.delta_generator.DeltaGenerator
@@ -108,9 +108,7 @@ def plot_trajectories(
line_width = 1.5
plot_start = True
- add_trace_for_direction(
- fig, df, id_, color_choice, line_width, framerate, plot_start
- )
+ add_trace_for_direction(fig, df, id_, color_choice, line_width, framerate, plot_start)
# geometry
fig.add_trace(
@@ -125,9 +123,7 @@ def plot_trajectories(
count_direction = ""
for name in ["North", "South", "East", "West"]:
count = directions[directions["direction_name"] == name].shape[0]
- count_direction += (
- f" {name} {count}."
- )
+ count_direction += f" {name} {count}."
fig.update_layout(
title=f" Trajectories: {num_agents}. {count_direction}",
@@ -256,9 +252,7 @@ def plot_time_series(density: pd.DataFrame, speed: pd.DataFrame, fps: int) -> go
return fig
-def plt_plot_time_series(
- density: pd.DataFrame, speed: pd.DataFrame, fps: int
-) -> Tuple[matplotlib.figure.Figure, matplotlib.figure.Figure]:
+def plt_plot_time_series(density: pd.DataFrame, speed: pd.DataFrame, fps: int) -> Tuple[matplotlib.figure.Figure, matplotlib.figure.Figure]:
"""Plot density and speed time series side-byside."""
fs = 18
# density
@@ -290,9 +284,7 @@ def plt_plot_time_series(
return fig1, fig2
-def plot_fundamental_diagram_all(
- density_dict: Dict[str, pd.DataFrame], speed_dict: Dict[str, pd.DataFrame]
-) -> go.Figure:
+def plot_fundamental_diagram_all(density_dict: Dict[str, pd.DataFrame], speed_dict: Dict[str, pd.DataFrame]) -> go.Figure:
"""Plot fundamental diagram of all files."""
fig = go.Figure()
colors_const = [
@@ -324,9 +316,7 @@ def plot_fundamental_diagram_all(
colors.append(color)
filenames.append(filename)
- for i, (density, speed) in enumerate(
- zip(density_dict.values(), speed_dict.values())
- ):
+ for i, (density, speed) in enumerate(zip(density_dict.values(), speed_dict.values())):
if isinstance(speed, pd.Series):
y = speed
else:
@@ -360,9 +350,7 @@ def plot_fundamental_diagram_all(
return fig
-def plot_x_y(
- x: pd.Series, y: pd.Series, title: str, xlabel: str, ylabel: str, color: str
-) -> Tuple[Scatter, Figure]:
+def plot_x_y(x: pd.Series, y: pd.Series, title: str, xlabel: str, ylabel: str, color: str) -> Tuple[Scatter, Figure]:
"""Plot two arrays and return trace and fig."""
fig = make_subplots(
rows=1,
@@ -386,9 +374,7 @@ def plot_x_y(
return trace, fig
-def plot_fundamental_diagram_all_mpl(
- density_dict: Dict[str, pd.DataFrame], speed_dict: Dict[str, pd.DataFrame]
-) -> matplotlib.figure.Figure:
+def plot_fundamental_diagram_all_mpl(density_dict: Dict[str, pd.DataFrame], speed_dict: Dict[str, pd.DataFrame]) -> matplotlib.figure.Figure:
"""Plot fundamental diagram of all files using Matplotlib."""
# Define colors and marker styles
colors_const = [
@@ -415,15 +401,11 @@ def plot_fundamental_diagram_all_mpl(
] # Matplotlib marker styles
fig, ax = plt.subplots()
- for i, ((filename, density), (_, speed)) in enumerate(
- zip(density_dict.items(), speed_dict.items())
- ):
+ for i, ((filename, density), (_, speed)) in enumerate(zip(density_dict.items(), speed_dict.items())):
if isinstance(speed, pd.Series):
y = speed
else:
- y = speed[
- "speed"
- ] # Adjust this if 'speed' DataFrame structure is different
+ y = speed["speed"] # Adjust this if 'speed' DataFrame structure is different
ax.scatter(
density["density"],
@@ -465,21 +447,15 @@ def assign_direction_number(agent_data: pd.DataFrame) -> pd.DataFrame:
# Determine primary direction of motion
if abs(delta_x) > abs(delta_y):
# Motion is primarily horizontal
- direction_number = (
- 3 if delta_x > 0 else 4
- ) # East if delta_x positive, West otherwise
+ direction_number = 3 if delta_x > 0 else 4 # East if delta_x positive, West otherwise
direction_name = "East" if delta_x > 0 else "West"
else:
# Motion is primarily vertical
- direction_number = (
- 1 if delta_y > 0 else 2
- ) # North if delta_y positive, South otherwise
+ direction_number = 1 if delta_y > 0 else 2 # North if delta_y positive, South otherwise
direction_name = "North" if delta_x > 0 else "South"
direction_numbers.append((agent_id, direction_number, direction_name))
- return pd.DataFrame(
- direction_numbers, columns=["id", "direction_number", "direction_name"]
- )
+ return pd.DataFrame(direction_numbers, columns=["id", "direction_number", "direction_name"])
def show_fig(
@@ -508,9 +484,7 @@ def show_fig(
fig.write_image(figname)
-def download_file(
- figname: str, col: Optional[st_column] = None, label: str = ""
-) -> None:
+def download_file(figname: str, col: Optional[st_column] = None, label: str = "") -> None:
"""Make download button for file."""
with open(figname, "rb") as pdf_file:
if col is None:
@@ -533,9 +507,7 @@ def download_file(
)
-def get_scaled_dimensions(
- geominX: float, geomaxX: float, geominY: float, geomaxY: float
-) -> Tuple[float, float, float]:
+def get_scaled_dimensions(geominX: float, geomaxX: float, geominY: float, geomaxY: float) -> Tuple[float, float, float]:
"""Return with, height and scale for background image."""
scale = np.amin((geomaxX - geominX, geomaxY - geominY))
scale_max = 20
@@ -567,9 +539,7 @@ def plot_trajectories_mpl(
)
-def bg_img(
- data: pd.DataFrame, geominX: float, geomaxX: float, geominY: float, geomaxY: float
-) -> Tuple[Image, float, float, float, float]:
+def draw_bg_img(data: pd.DataFrame, geominX: float, geomaxX: float, geominY: float, geomaxY: float) -> Tuple[Image, float, float, float, float]:
"""Plot trajectories and create a background image."""
logging.info("enter bg_img")
width, height, scale = get_scaled_dimensions(geominX, geomaxX, geominY, geomaxY)
diff --git a/calculate_profiles.py b/src/scripts/calculate_profiles.py
similarity index 99%
rename from calculate_profiles.py
rename to src/scripts/calculate_profiles.py
index db49856..637a7ef 100644
--- a/calculate_profiles.py
+++ b/src/scripts/calculate_profiles.py
@@ -11,13 +11,12 @@
import pandas as pd
import pedpy as pp
from joblib import Parallel, delayed
+from log_config import setup_logging
from pedpy import (DensityMethod, SpeedMethod,
compute_grid_cell_polygon_intersection_area,
compute_speed_profile, get_grid_cells)
-from tqdm import tqdm
-
-from log_config import setup_logging
from profile_config_data import Config
+from tqdm import tqdm
def process_file(
diff --git a/src/scripts/check.sh b/src/scripts/check.sh
new file mode 100755
index 0000000..3f7e883
--- /dev/null
+++ b/src/scripts/check.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# Define the path to the virtual environment's bin directory
+VENV_BIN="../../.venv/bin"
+
+if [ ! -d "$VENV_BIN" ]; then
+ echo "Virtual environment not found at $VENV_BIN."
+ exit 1
+fi
+
+PYTHON_FILES=$(git ls-files '*.py')
+
+if [ -z "$PYTHON_FILES" ]; then
+ echo "No Python files found to check."
+ exit 0
+fi
+
+run_tool() {
+ echo ">> $1..."
+ $VENV_BIN/$1 $2 $PYTHON_FILES
+}
+
+# Static type checker
+run_tool mypy "--strict"
+
+# Fast, extensible Python linter
+run_tool ruff "check"
+
+# Sort imports
+run_tool isort "check"
+
+echo "All checks and formatting done."
diff --git a/create_profile_data.py b/src/scripts/create_profile_data.py
similarity index 99%
rename from create_profile_data.py
rename to src/scripts/create_profile_data.py
index 1946b79..43f3b4d 100644
--- a/create_profile_data.py
+++ b/src/scripts/create_profile_data.py
@@ -8,11 +8,10 @@
import pandas as pd
import pedpy as pp
from joblib import Parallel, delayed
-from pedpy.column_identifier import FRAME_COL, ID_COL
-from tqdm import tqdm
-
from log_config import setup_logging
+from pedpy.column_identifier import FRAME_COL, ID_COL
from profile_config_data import Config
+from tqdm import tqdm
def process_trajectory(
diff --git a/ploting_profiles.py b/src/scripts/ploting_profiles.py
similarity index 99%
rename from ploting_profiles.py
rename to src/scripts/ploting_profiles.py
index 37b24ec..5dba067 100644
--- a/ploting_profiles.py
+++ b/src/scripts/ploting_profiles.py
@@ -9,7 +9,6 @@
import matplotlib.pyplot as plt
import numpy as np
import pedpy as pp
-
from log_config import setup_logging
from profile_config_data import Config
diff --git a/profile_config_data.py b/src/scripts/profile_config_data.py
similarity index 100%
rename from profile_config_data.py
rename to src/scripts/profile_config_data.py
diff --git a/run_profile_calculations.py b/src/scripts/run_profile_calculations.py
similarity index 64%
rename from run_profile_calculations.py
rename to src/scripts/run_profile_calculations.py
index aa97f56..18c12b0 100644
--- a/run_profile_calculations.py
+++ b/src/scripts/run_profile_calculations.py
@@ -4,26 +4,29 @@
"""
import glob
-
-from pedpy import WalkableArea
+from pathlib import Path
import calculate_profiles
import create_profile_data
import ploting_profiles
+from pedpy import WalkableArea
from profile_config_data import Config
def run_all() -> None:
"""Initialize the configuration."""
+ path = Path(__file__)
+ data_directory = path.parent.parent.parent.absolute() / "data" / "processed"
+ trajectories_directory = path.parent.parent.parent.absolute() / "data" / "trajectories"
area = [[-6, 0], [5, 0], [5, 7], [-6, 7]]
config = Config(
- files=sorted(glob.glob("AppData/*.txt")),
+ files=sorted(glob.glob(f"{str(trajectories_directory)}/*.txt")),
grid_size=0.4,
speed_frame_rate=10,
fps=30,
walkable_area=WalkableArea(area),
- profile_data_file="AppData/profile_data.pkl",
- result_file="AppData/density_speed_profiles.pkl",
+ profile_data_file=f"{str(data_directory)}/profile_data.pkl",
+ result_file=f"{str(data_directory)}/density_speed_profiles.pkl",
rmax=3.0,
vmax=1.2,
jmax=2.0,
diff --git a/analysis_tab.py b/src/tabs/analysis_tab.py
similarity index 78%
rename from analysis_tab.py
rename to src/tabs/analysis_tab.py
index 37b04fe..ff60778 100644
--- a/analysis_tab.py
+++ b/src/tabs/analysis_tab.py
@@ -14,11 +14,14 @@
import pedpy
import streamlit as st
-import datafactory
-import docs
-import drawing
-import plots
-import utilities
+from ..classes.datafactory import load_file
+from ..docs.docs import density_speed, flow
+from ..helpers.utilities import (download, get_measurement_lines,
+ is_running_locally, setup_walkable_area)
+from ..plotting.drawing import drawing_canvas, get_measurement_area
+from ..plotting.plots import (download_file, plot_fundamental_diagram_all,
+ plot_fundamental_diagram_all_mpl,
+ plot_time_series, plt_plot_time_series, show_fig)
st_column: TypeAlias = st.delta_generator.DeltaGenerator
@@ -29,11 +32,9 @@ def calculate_or_load_classical_density(
) -> pd.DataFrame:
"""Calculate classical density or load existing calculation."""
if not Path(precalculated_density).exists():
- trajectory_data = datafactory.load_file(filename)
- walkable_area = utilities.setup_walkable_area(trajectory_data)
- classic_density = pedpy.compute_classic_density(
- traj_data=trajectory_data, measurement_area=walkable_area
- )
+ trajectory_data = load_file(filename)
+ walkable_area = setup_walkable_area(trajectory_data)
+ classic_density = pedpy.compute_classic_density(traj_data=trajectory_data, measurement_area=walkable_area)
with open(precalculated_density, "wb") as f:
pickle.dump(classic_density, f)
else:
@@ -50,18 +51,14 @@ def calculate_or_load_voronoi_diagrams(
) -> pd.DataFrame:
"""Calculate Voronoi diagrams or load existing calculation."""
if not Path(precalculated_voronoi_polygons).exists():
- trajectory_data = datafactory.load_file(filename)
- walkable_area = utilities.setup_walkable_area(trajectory_data)
- voronoi_polygons = pedpy.compute_individual_voronoi_polygons(
- traj_data=trajectory_data, walkable_area=walkable_area
- )
+ trajectory_data = load_file(filename)
+ walkable_area = setup_walkable_area(trajectory_data)
+ voronoi_polygons = pedpy.compute_individual_voronoi_polygons(traj_data=trajectory_data, walkable_area=walkable_area)
with open(precalculated_voronoi_polygons, "wb") as f:
pickle.dump(voronoi_polygons, f, pickle.HIGHEST_PROTOCOL)
else:
- logging.info(
- f"load precalculated voronoi polygons: {precalculated_voronoi_polygons}"
- )
+ logging.info(f"load precalculated voronoi polygons: {precalculated_voronoi_polygons}")
with open(precalculated_voronoi_polygons, "rb") as f:
voronoi_polygons = pickle.load(f)
@@ -76,8 +73,8 @@ def calculate_or_load_voronoi_speed(
) -> pd.Series:
"""Calculate Voronoi speed or load existing calculation."""
if not Path(precalculated_voronoi_speed).exists():
- trajectory_data = datafactory.load_file(filename)
- walkable_area = utilities.setup_walkable_area(trajectory_data)
+ trajectory_data = load_file(filename)
+ walkable_area = setup_walkable_area(trajectory_data)
voronoi_speed = pedpy.compute_voronoi_speed(
traj_data=trajectory_data,
individual_voronoi_intersection=intersecting,
@@ -101,8 +98,8 @@ def calculate_or_load_voronoi_density(
) -> Tuple[pd.DataFrame, pd.DataFrame]:
"""Calculate Voronoi density or load existing calculation."""
if not Path(precalculated_voronoi_density).exists():
- trajectory_data = datafactory.load_file(filename)
- walkable_area = utilities.setup_walkable_area(trajectory_data)
+ trajectory_data = load_file(filename)
+ walkable_area = setup_walkable_area(trajectory_data)
voronoi_density, intersecting = pedpy.compute_voronoi_density(
individual_voronoi_data=voronoi_polygons,
measurement_area=walkable_area,
@@ -111,21 +108,17 @@ def calculate_or_load_voronoi_density(
with open(precalculated_voronoi_density, "wb") as f:
pickle.dump((voronoi_density, intersecting), f, pickle.HIGHEST_PROTOCOL)
else:
- logging.info(
- f"load precalculated voronoi density: {precalculated_voronoi_density}"
- )
+ logging.info(f"load precalculated voronoi density: {precalculated_voronoi_density}")
with open(precalculated_voronoi_density, "rb") as f:
voronoi_density, intersecting = pickle.load(f)
return voronoi_density, intersecting
-def calculate_or_load_individual_speed(
- precalculated_speed: str, filename: str, dv: Optional[int]
-) -> pd.DataFrame:
+def calculate_or_load_individual_speed(precalculated_speed: str, filename: str, dv: Optional[int]) -> pd.DataFrame:
"""Calculate speed or load precalculated values if exist."""
if not Path(precalculated_speed).exists():
- trajectory_data = datafactory.load_file(filename)
+ trajectory_data = load_file(filename)
individual_speed = pedpy.compute_individual_speed(
traj_data=trajectory_data,
frame_step=dv,
@@ -141,12 +134,10 @@ def calculate_or_load_individual_speed(
return individual_speed
-def calculate_or_load_mean_speed(
- precalculated_speed: str, filename: str, dv: Optional[int]
-) -> pd.DataFrame:
+def calculate_or_load_mean_speed(precalculated_speed: str, filename: str, dv: Optional[int]) -> pd.DataFrame:
speed = calculate_or_load_individual_speed(precalculated_speed, filename, dv)
- trajectory_data = datafactory.load_file(filename)
- walkable_area = utilities.setup_walkable_area(trajectory_data)
+ trajectory_data = load_file(filename)
+ walkable_area = setup_walkable_area(trajectory_data)
return pedpy.compute_mean_speed_per_frame(
traj_data=trajectory_data,
measurement_area=walkable_area,
@@ -161,17 +152,11 @@ def calculate_time_series(
selected_file: str,
) -> None:
"""Calculate speed and density."""
- docs_expander = st.expander(
- ":orange_book: Documentation (click to expand)", expanded=False
- )
+ docs_expander = st.expander(":orange_book: Documentation (click to expand)", expanded=False)
with docs_expander:
- docs.density_speed()
- canvas, dpi, scale, img_height = drawing.drawing_canvas(
- trajectory_data, walkable_area
- )
- measurement_areas = drawing.get_measurement_area(
- trajectory_data, canvas, dpi, scale, img_height
- )
+ density_speed()
+ canvas, dpi, scale, img_height = drawing_canvas(trajectory_data, walkable_area)
+ measurement_areas = get_measurement_area(trajectory_data, canvas, dpi, scale, img_height)
individual_speed = pedpy.compute_individual_speed(
traj_data=trajectory_data,
frame_step=dv,
@@ -183,42 +168,24 @@ def calculate_time_series(
measurement_area=ma,
individual_speed=individual_speed,
)
- classic_density = pedpy.compute_classic_density(
- traj_data=trajectory_data, measurement_area=ma
- )
- fig = plots.plot_time_series(classic_density, mean_speed, fps=30)
- plots.show_fig(fig, html=True, write=False)
+ classic_density = pedpy.compute_classic_density(traj_data=trajectory_data, measurement_area=ma)
+ fig = plot_time_series(classic_density, mean_speed, fps=30)
+ show_fig(fig, html=True, write=False)
# for plots
- pfig1, pfig2 = plots.plt_plot_time_series(classic_density, mean_speed, fps=30)
+ pfig1, pfig2 = plt_plot_time_series(classic_density, mean_speed, fps=30)
c1, c2 = st.columns(2)
c1.pyplot(pfig1)
c2.pyplot(pfig2)
bounds = ma.polygon.bounds
- formatted_bounds = (
- f"({bounds[0]:.2f}, {bounds[1]:.2f}, {bounds[2]:.2f}, {bounds[3]:.2f})"
- )
- st.info(
- f"Measurement area coordinates: {formatted_bounds}, Area: {ma.area:.2} $m^2$."
- )
+ formatted_bounds = f"({bounds[0]:.2f}, {bounds[1]:.2f}, {bounds[2]:.2f}, {bounds[3]:.2f})"
+ st.info(f"Measurement area coordinates: {formatted_bounds}, Area: {ma.area:.2} $m^2$.")
# download figures
- figname1 = (
- "density_"
- + selected_file.split("/")[-1].split(".txt")[0]
- + "_ma_"
- + str(mai)
- + ".pdf"
- )
+ figname1 = "density_" + selected_file.split("/")[-1].split(".txt")[0] + "_ma_" + str(mai) + ".pdf"
pfig1.savefig(figname1, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname1, c1, "density")
- figname2 = (
- "speed_"
- + selected_file.split("/")[-1].split(".txt")[0]
- + "_ma_"
- + str(mai)
- + ".pdf"
- )
+ download_file(figname1, c1, "density")
+ figname2 = "speed_" + selected_file.split("/")[-1].split(".txt")[0] + "_ma_" + str(mai) + ".pdf"
pfig2.savefig(figname2, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname2, c2, "speed")
+ download_file(figname2, c2, "speed")
def calculate_fd_classical(dv: Optional[int]) -> None:
@@ -232,21 +199,17 @@ def calculate_fd_classical(dv: Optional[int]) -> None:
basename = filename.split("/")[-1].split(".txt")[0]
precalculated_density = f"AppData/density_{basename}.pkl"
precalculated_speed = f"AppData/speed_{basename}_{dv}.pkl"
- speeds[basename] = calculate_or_load_mean_speed(
- precalculated_speed, filename, dv
- )
- densities[basename] = calculate_or_load_classical_density(
- precalculated_density, filename
- )
+ speeds[basename] = calculate_or_load_mean_speed(precalculated_speed, filename, dv)
+ densities[basename] = calculate_or_load_classical_density(precalculated_density, filename)
progress = int(100 * (i + 1) / len(st.session_state.files))
progress_bar.progress(progress)
progress_status.text(f"> {progress}%")
figname = "fundamental_diagram_classical.pdf"
- fig = plots.plot_fundamental_diagram_all_mpl(densities, speeds)
+ fig = plot_fundamental_diagram_all_mpl(densities, speeds)
fig.savefig(figname, bbox_inches="tight", pad_inches=0.1)
st.pyplot(fig)
- plots.download_file(figname)
+ download_file(figname)
# plots.show_fig(fig, figname=figname, html=True, write=True)
@@ -261,10 +224,8 @@ def calculate_fd_voronoi_local(dv: Optional[int]) -> None:
figname = "AppData/fundamental_diagram_voronoi.pdf"
st.sidebar.divider()
msg = st.sidebar.empty()
- calculate = msg.button(
- "Calculate", type="primary", help="Calculate fundamental diagram Voronoi"
- )
- if not utilities.is_running_locally():
+ calculate = msg.button("Calculate", type="primary", help="Calculate fundamental diagram Voronoi")
+ if not is_running_locally():
st.warning(
"""
This calculation is disabled when running in a deployed environment.\n
@@ -272,11 +233,9 @@ def calculate_fd_voronoi_local(dv: Optional[int]) -> None:
"""
)
st.code("streamlit run app.py")
- st.warning(
- "See [README](https://github.com/PedestrianDynamics/madras-data-app?tab=readme-ov-file#local-execution-guide) for more information."
- )
+ st.warning("See [README](https://github.com/PedestrianDynamics/madras-data-app?tab=readme-ov-file#local-execution-guide) for more information.")
- if utilities.is_running_locally() and calculate:
+ if is_running_locally() and calculate:
with st.status("Calculating Voronoi FD ...", expanded=True):
progress_bar = st.progress(0)
progress_status = st.empty()
@@ -284,32 +243,22 @@ def calculate_fd_voronoi_local(dv: Optional[int]) -> None:
for i, filename in enumerate(st.session_state.files):
basename = filename.split("/")[-1].split(".txt")[0]
# saved files ============
- precalculated_voronoi_polygons = (
- f"AppData/voronoi_polygons_{basename}.pkl"
- )
+ precalculated_voronoi_polygons = f"AppData/voronoi_polygons_{basename}.pkl"
precalculated_speed = f"AppData/speed_{basename}_{dv}.pkl"
precalculated_voronoi_speed = f"AppData/voronoi_speed_{basename}.pkl"
- precalculated_voronoi_density = (
- f"AppData/voronoi_density_{basename}.pkl"
- )
+ precalculated_voronoi_density = f"AppData/voronoi_density_{basename}.pkl"
# saved files ============
- voronoi_polygons[basename] = calculate_or_load_voronoi_diagrams(
- precalculated_voronoi_polygons, filename
- )
+ voronoi_polygons[basename] = calculate_or_load_voronoi_diagrams(precalculated_voronoi_polygons, filename)
- individual_speed[basename] = calculate_or_load_individual_speed(
- precalculated_speed, filename, dv
- )
+ individual_speed[basename] = calculate_or_load_individual_speed(precalculated_speed, filename, dv)
# todo save to files
# trajectory_data = datafactory.load_file(filename)
- # walkable_area = utilities.setup_walkable_area(trajectory_data)
-
- voronoi_density[basename], intersecting[basename] = (
- calculate_or_load_voronoi_density(
- precalculated_voronoi_density,
- voronoi_polygons[basename],
- filename,
- )
+ # walkable_area = setup_walkable_area(trajectory_data)
+
+ voronoi_density[basename], intersecting[basename] = calculate_or_load_voronoi_density(
+ precalculated_voronoi_density,
+ voronoi_polygons[basename],
+ filename,
)
voronoi_speed[basename] = calculate_or_load_voronoi_speed(
precalculated_voronoi_speed,
@@ -325,14 +274,14 @@ def calculate_fd_voronoi_local(dv: Optional[int]) -> None:
with open(voronoi_results, "wb") as f:
pickle.dump((voronoi_density, voronoi_speed), f, pickle.HIGHEST_PROTOCOL)
- fig = plots.plot_fundamental_diagram_all(voronoi_density, voronoi_speed)
+ fig = plot_fundamental_diagram_all(voronoi_density, voronoi_speed)
- plots.show_fig(fig, figname=figname, html=True, write=True)
+ show_fig(fig, figname=figname, html=True, write=True)
end = time.time()
st.info(f"Computation time: {end-start:.2f} seconds.")
if calculate and Path(figname).exists():
- plots.download_file(figname, msg)
+ download_file(figname, msg)
if not Path(figname).exists():
st.warning(f"File {figname} does not exist yet! You should calculate it first")
@@ -348,23 +297,23 @@ def download_fd_voronoi() -> None:
if not Path(voronoi_results).exists():
msg.warning(f"{voronoi_results} does not exist yet!")
with st.status("Downloading ...", expanded=True):
- utilities.download(url, voronoi_results)
+ download(url, voronoi_results)
if Path(voronoi_results).exists():
with open(voronoi_results, "rb") as f:
voronoi_density, voronoi_speed = pickle.load(f)
- fig = plots.plot_fundamental_diagram_all_mpl(voronoi_density, voronoi_speed)
+ fig = plot_fundamental_diagram_all_mpl(voronoi_density, voronoi_speed)
st.pyplot(fig)
fig.savefig(figname, bbox_inches="tight", pad_inches=0.1)
- # plots.download_file(figname)
+ # download_file(figname)
# fig = plots.plot_fundamental_diagram_all(voronoi_density, voronoi_speed)
# plots.show_fig(fig, figname=figname, html=True, write=True)
if Path(figname).exists():
- plots.download_file(figname)
+ download_file(figname)
else:
st.warning(f"File {figname} does not exist yet! You should calculate it first")
@@ -385,12 +334,10 @@ def calculate_nt(
help="Distance of the meansurement lines to the edges of the geometry.",
format="%.2f",
)
- directions = utilities.get_measurement_lines(trajectory_data, distance_to_bounding)
- docs_expander = st.expander(
- ":orange_book: Documentation (click to expand)", expanded=False
- )
+ directions = get_measurement_lines(trajectory_data, distance_to_bounding)
+ docs_expander = st.expander(":orange_book: Documentation (click to expand)", expanded=False)
with docs_expander:
- docs.flow(directions)
+ flow(directions)
names = [direction.info.name for direction in directions]
colors = [direction.info.color for direction in directions]
@@ -401,9 +348,7 @@ def calculate_nt(
nt_stats = {}
for i, (name, color) in enumerate(zip(selected_names, colors)):
direction = directions[i]
- nt, _ = pedpy.compute_n_t(
- traj_data=trajectory_data, measurement_line=direction.line
- )
+ nt, _ = pedpy.compute_n_t(traj_data=trajectory_data, measurement_line=direction.line)
figname += f"_{name}"
pedpy.plot_nt(
nt=nt,
@@ -427,7 +372,7 @@ def calculate_nt(
c2.dataframe(pd.DataFrame(nt_stats))
figname += ".pdf"
fig1.savefig(figname, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname)
+ download_file(figname)
def calculate_density_profile(
@@ -514,7 +459,7 @@ def calculate_density_profile(
st.pyplot(fig)
plt.tight_layout()
fig.savefig(figname, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname)
+ download_file(figname)
def calculate_speed_profile(
@@ -596,7 +541,7 @@ def calculate_speed_profile(
figname = f"speed_profile_fil_{fil_empty}_grid_{grid_size}_{base_filename}.pdf"
st.pyplot(fig)
fig.savefig(figname, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname)
+ download_file(figname)
def ui_tab3_analysis() -> Tuple[str, Optional[int], st_column]:
@@ -616,7 +561,7 @@ def ui_tab3_analysis() -> Tuple[str, Optional[int], st_column]:
st.error(f"Error deleting {file_path}: {e}")
st.sidebar.divider()
- if utilities.is_running_locally():
+ if is_running_locally():
calculations = str(
st.radio(
"**Choose calculation**",
@@ -669,12 +614,12 @@ def ui_tab3_analysis() -> Tuple[str, Optional[int], st_column]:
def prepare_data(selected_file: str) -> Tuple[pedpy.TrajectoryData, List[List[float]]]:
"""Load file, setup state_session and get walkable_area."""
if selected_file != st.session_state.file_changed:
- trajectory_data = datafactory.load_file(selected_file)
+ trajectory_data = load_file(selected_file)
st.session_state.trajectory_data = trajectory_data
st.session_state.file_changed = selected_file
trajectory_data = st.session_state.trajectory_data
- walkable_area = utilities.setup_walkable_area(trajectory_data)
+ walkable_area = setup_walkable_area(trajectory_data)
return trajectory_data, walkable_area
@@ -682,14 +627,16 @@ def prepare_data(selected_file: str) -> Tuple[pedpy.TrajectoryData, List[List[fl
def run_tab3() -> None:
"""Run the main logic in tab analysis."""
calculations, dv, c1 = ui_tab3_analysis()
+ file_name_to_path = {path.split("/")[-1]: path for path in st.session_state.files}
if not calculations.startswith("FD"):
- selected_file = str(
+ filename = str(
st.selectbox(
":open_file_folder: **Select a file**",
- st.session_state.files,
+ file_name_to_path,
key="tab3_filename",
)
)
+ selected_file = file_name_to_path[filename]
st.session_state.selected_file = selected_file
trajectory_data, walkable_area = prepare_data(selected_file)
diff --git a/contacts_tab.py b/src/tabs/contacts_tab.py
similarity index 76%
rename from contacts_tab.py
rename to src/tabs/contacts_tab.py
index e1277c3..6d3671d 100644
--- a/contacts_tab.py
+++ b/src/tabs/contacts_tab.py
@@ -1,5 +1,6 @@
-""" Map of the gps trajectories coupled with the contacts locations. """
+"""Map of the gps trajectories coupled with the contacts locations."""
+from pathlib import Path
from typing import Tuple
import folium
@@ -13,11 +14,10 @@
from plotly.graph_objects import Figure
from streamlit_folium import st_folium
-from plots import download_file
+from ..plotting.plots import download_file
-def load_data(
- gps_path: str, contacts_path: str
-) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
+
+def load_data(gps_path: str, contacts_path: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
"""
Load GPS tracks and contact data from pickled files.
@@ -28,9 +28,9 @@ def load_data(
Returns:
tuple: A tuple containing all GPS tracks and contact GPS merged data.
"""
- all_gps_tracks = pd.read_pickle(gps_path + "all_gps_tracks.pickle")
- contact_gps_merged = pd.read_pickle(contacts_path + "contacts_gps_merged.pickle")
- contacts_data = pd.read_pickle(contacts_path + "contacts_data.pickle")
+ all_gps_tracks = pd.read_pickle(gps_path + "/" + "all_gps_tracks.pickle")
+ contact_gps_merged = pd.read_pickle(contacts_path + "/" + "contacts_gps_merged.pickle")
+ contacts_data = pd.read_pickle(contacts_path + "/" + "contacts_data.pickle")
return all_gps_tracks, contact_gps_merged, contacts_data
@@ -45,9 +45,7 @@ def initialize_map(all_gps_tracks: pd.DataFrame) -> folium.Map:
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 = first_track_df.iloc[len(first_track_df) // 2][["latitude", "longitude"]].tolist()
return folium.Map(location=map_center, zoom_start=17.5)
@@ -84,14 +82,10 @@ 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, popup=name_subj).add_to(map_object)
-def add_contact_markers(
- map_object: folium.Map, contact_gps_merged: pd.DataFrame, path_icon: str
-) -> None:
+def add_contact_markers(map_object: folium.Map, contact_gps_merged: pd.DataFrame, path_icon: str) -> None:
"""
Add markers for each contact point on the map.
@@ -100,9 +94,7 @@ def add_contact_markers(
contact_gps_merged (pd.DataFrame): DataFrame containing contact GPS merged data.
"""
for index, row in contact_gps_merged.iterrows():
- icon_person = folium.features.CustomIcon(
- icon_image=path_icon + "/contact_icon.png", icon_size=(30, 30)
- )
+ icon_person = folium.features.CustomIcon(icon_image=path_icon + "/contact_icon.png", icon_size=(30, 30))
folium.Marker(
location=[row["latitude"], row["longitude"]],
icon=icon_person,
@@ -130,11 +122,7 @@ def plot_histogram(df: pd.DataFrame, bins: int) -> Figure:
text_auto=True,
title="Histogram of the total number of collisions",
)
- 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.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
return fig
@@ -148,13 +136,9 @@ def plot_cumulative_contacts(df: pd.DataFrame) -> Figure:
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()])
- )
+ 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", name=row["Name"]))
# Update layout of the figure
fig.update_layout(
@@ -174,9 +158,9 @@ def main() -> None:
st.title("Map of GPS Trajectories coupled with contacts locations.")
# Paths to the data directories
- path_data = "./App_data_contacts/"
- path_icon = "./logo_contact/"
-
+ path = Path(__file__)
+ path_data = str(path.parent.parent.parent.absolute() / "data" / "contacts")
+ path_icon = str(path.parent.parent.parent.absolute() / "data" / "assets" / "logo_contact")
# Load GPS tracks and contact data
all_gps_tracks, contact_gps_merged, contacts_data = load_data(path_data, path_data)
@@ -190,9 +174,7 @@ def main() -> None:
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
- ))
+ 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)
figname = f"histogram_{bins}.pdf"
@@ -200,5 +182,5 @@ def main() -> None:
download_file(figname)
-def call_main() -> None:
+def run_tab_contact() -> None:
main()
diff --git a/map_tab.py b/src/tabs/map_tab.py
similarity index 88%
rename from map_tab.py
rename to src/tabs/map_tab.py
index 21d690a..73f1198 100644
--- a/map_tab.py
+++ b/src/tabs/map_tab.py
@@ -2,6 +2,7 @@
import json
from dataclasses import dataclass
+from pathlib import Path
from typing import Dict, List, Tuple, cast
import folium
@@ -53,20 +54,14 @@ def load_cameras_from_json(file_path: str) -> Dict[str, Camera]:
try:
# Ensure the data structure is as expected
location = tuple(info["location"])
- assert (
- isinstance(location, tuple) and len(location) == 2
- ), "Location must be a tuple of two floats."
- assert all(
- isinstance(x, float) for x in location
- ), "Location elements must be floats."
+ assert isinstance(location, tuple) and len(location) == 2, "Location must be a tuple of two floats."
+ assert all(isinstance(x, float) for x in location), "Location elements must be floats."
location = cast(Tuple[float, float], location)
url = info["url"]
name = info["name"]
field = info["field"]
logo = info["logo"]
- cameras[key] = Camera(
- location=location, url=url, name=name, field=field, logo=logo
- )
+ cameras[key] = Camera(location=location, url=url, name=name, field=field, logo=logo)
except KeyError as e:
# Handle missing keys in the data
@@ -95,6 +90,8 @@ def create_map(
Returns:
folium.Map: A folium map object.
"""
+ 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)
camera_layers = []
@@ -156,7 +153,8 @@ def create_map(
polygon.add_to(layer)
for (key, camera), layer in zip(cameras.items(), camera_layers):
- icon = CustomIcon(camera.logo, icon_size=(110, 110))
+ camera_path = logo_cameras / camera.logo
+ icon = CustomIcon(str(camera_path), icon_size=(110, 110))
coords = camera.location
tooltip = f"{key}: {camera.name}"
folium.Marker(location=coords, tooltip=tooltip, icon=icon).add_to(layer)
@@ -196,8 +194,10 @@ def main(cameras: Dict[str, Camera], selected_layer: str) -> None:
placeholder.error("No camera selected.")
-def call_main() -> None:
- cameras = load_cameras_from_json("cameras.json")
+def run_tab_map() -> None:
+ path = Path(__file__).parent.parent.parent.absolute()
+ json_path = path / "data" / "assets" / "cameras.json"
+ cameras = load_cameras_from_json(str(json_path))
selected_layer = st.selectbox("Choose a Map Style:", list(tile_layers.keys()))
selected_layer = str(selected_layer)
st.markdown(
diff --git a/traj_tab.py b/src/tabs/traj_tab.py
similarity index 87%
rename from traj_tab.py
rename to src/tabs/traj_tab.py
index 368327b..19438aa 100644
--- a/traj_tab.py
+++ b/src/tabs/traj_tab.py
@@ -8,10 +8,10 @@
from shapely import Polygon
from streamlit.delta_generator import DeltaGenerator
-import datafactory
-# import docs
-import plots
-from anim import animate
+from ..classes.datafactory import load_file
+from ..plotting.anim import animate
+from ..plotting.plots import (download_file, plot_trajectories,
+ plot_trajectories_figure_mpl)
# import cProfile
# import pstats
@@ -24,7 +24,7 @@ def run_tab2(selected_file: str, msg: DeltaGenerator) -> None:
# todo
msg.write("")
if selected_file != st.session_state.file_changed:
- trajectory_data = datafactory.load_file(selected_file)
+ trajectory_data = load_file(selected_file)
st.session_state.trajectory_data = trajectory_data
st.session_state.file_changed = selected_file
@@ -41,9 +41,7 @@ def run_tab2(selected_file: str, msg: DeltaGenerator) -> None:
frame_step=5,
speed_calculation=pedpy.SpeedCalculation.BORDER_SINGLE_SIDED,
)
- data_with_speed = data_with_speed.merge(
- trajectory_data.data, on=["id", "frame"], how="left"
- )
+ data_with_speed = data_with_speed.merge(trajectory_data.data, on=["id", "frame"], how="left")
ids = trajectory_data.data["id"].unique()
start_time = time.time()
@@ -89,7 +87,7 @@ def run_tab2(selected_file: str, msg: DeltaGenerator) -> None:
help="Visualize pedestrians moving in a direction.",
)
)
- fig = plots.plot_trajectories(
+ fig = plot_trajectories(
trajectory_data,
sample_frame,
walkable_area,
@@ -112,20 +110,14 @@ def run_tab2(selected_file: str, msg: DeltaGenerator) -> None:
min_value=0.01,
max_value=5.0,
)
-
- fig2 = plots.plot_trajectories_figure_mpl(
- trajectory_data, walkable_area, with_colors=True, alpha=alpha, lw=lw
- )
+ # TODO: remove
+ fig2 = plot_trajectories_figure_mpl(trajectory_data, walkable_area, with_colors=True, alpha=alpha, lw=lw)
# pfig, ax = plt.subplots()
# pedpy.plot_trajectories(traj=trajectory_data, walkable_area=walkable_area, axes=ax)
c1.pyplot(fig2)
- figname = (
- "trajectories_"
- + selected_file.split("/")[-1].split(".txt")[0]
- + "_colors.pdf"
- )
+ figname = "trajectories_" + selected_file.split("/")[-1].split(".txt")[0] + "_colors.pdf"
fig2.savefig(figname, bbox_inches="tight", pad_inches=0.1)
- plots.download_file(figname, c1, label="color")
+ download_file(figname, c1, label="color")
# fig3z = plots.plot_trajectories_figure_mpl(trajectory_data, walkable_area, with_colors=False)
# c2.pyplot(fig3)
# figname = "trajectories_" + selected_file.split("/")[-1].split(".txt")[0] + "_gray.pdf"
diff --git a/ui.py b/src/ui/ui.py
similarity index 86%
rename from ui.py
rename to src/ui/ui.py
index ed165a9..a95ede0 100644
--- a/ui.py
+++ b/src/ui/ui.py
@@ -1,4 +1,4 @@
-"""Init ui and download AppData if not existing."""
+"""Init ui."""
from pathlib import Path
from typing import Any
@@ -24,17 +24,15 @@ def setup_app() -> None:
def init_app_looks() -> None:
path = Path(__file__)
- ROOT_DIR = path.parent.absolute()
-
+ ROOT_DIR = path.parent.parent.parent.absolute()
+ logo_path = ROOT_DIR / "data" / "assets" / "logo.png"
gh = "https://badgen.net/badge/icon/GitHub?icon=github&label"
repo = "https://github.com/PedestrianDynamics/madras-data-app"
repo_name = f"[![Repo]({gh})]({repo})"
c1, c2 = st.sidebar.columns((1.2, 0.5))
c2.markdown(repo_name, unsafe_allow_html=True)
- c1.write(
- "[![DOI](https://zenodo.org/badge/760394097.svg)](https://zenodo.org/doi/10.5281/zenodo.10694866)"
- )
- st.sidebar.image(f"{ROOT_DIR}/logo.png", use_column_width=True)
+ c1.write("[![DOI](https://zenodo.org/badge/760394097.svg)](https://zenodo.org/doi/10.5281/zenodo.10694866)")
+ st.sidebar.image(str(logo_path), use_column_width=True)
def init_sidebar() -> Any: