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

New directory structure of the app #4

Merged
merged 8 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file removed ._logo.png
Binary file not shown.
Binary file removed ._my_map.html
Binary file not shown.
49 changes: 24 additions & 25 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Binary file removed data.zip
Binary file not shown.
18 changes: 9 additions & 9 deletions cameras.json → data/assets/cameras.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
Empty file added src/__init__.py
Empty file.
Empty file added src/classes/__init__.py
Empty file.
19 changes: 8 additions & 11 deletions datafactory.py → src/classes/datafactory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Unsorted datastructure for the app."""

import glob
import logging
import os
import shutil
import zipfile
Expand Down Expand Up @@ -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 = [
Expand All @@ -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


Expand All @@ -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.

Expand Down
Empty file added src/docs/__init__.py
Empty file.
12 changes: 8 additions & 4 deletions docs.py → src/docs/docs.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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.",
)
Empty file added src/helpers/__init__.py
Empty file.
File renamed without changes.
18 changes: 5 additions & 13 deletions utilities.py → src/helpers/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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],
Expand Down
File renamed without changes.
File renamed without changes.
38 changes: 12 additions & 26 deletions anim.py → src/plotting/anim.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
code here. Use it at your own peril.
"""


from typing import Any, Dict, List, Tuple

import matplotlib.pyplot as plt
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()],
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand All @@ -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()
Expand All @@ -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"<b>{title_note + ' | ' if title_note else ''}N: {agent_count}</b>"
title = f"<b>{title_note + ' | ' if title_note else ''}Number of Agents: {initial_agent_count}. Frame: {frame_num}</b>"
frame_name = str(int(frame_num))
Expand Down
Loading