Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- README.md +5 -8
- app.py +117 -0
- requirements.txt +2 -0
README.md
CHANGED
|
@@ -1,13 +1,10 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
---
|
| 12 |
-
|
| 13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Behavioral ML in Virtual Crowds
|
| 3 |
+
emoji: 🧍♀️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: "5"
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math, random, time
|
| 2 |
+
from dataclasses import dataclass, field
|
| 3 |
+
from typing import List, Optional, Tuple
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from PIL import Image, ImageDraw
|
| 6 |
+
|
| 7 |
+
W, H = 900, 540
|
| 8 |
+
ROOM = (60, 40, W - 60, H - 40)
|
| 9 |
+
DT = 0.05
|
| 10 |
+
MAX_SPEED_BASE = 65.0
|
| 11 |
+
PERSON_RADIUS = 6
|
| 12 |
+
EXIT_W, EXIT_H = 28, 68
|
| 13 |
+
C_BG = (245, 246, 248, 255)
|
| 14 |
+
C_ROOM = (250, 250, 250, 255)
|
| 15 |
+
C_BORDER = (28, 28, 28, 255)
|
| 16 |
+
C_OBS = (175, 178, 185, 255)
|
| 17 |
+
C_EXIT = (52, 132, 255, 255)
|
| 18 |
+
C_CALM = (44, 184, 78, 255)
|
| 19 |
+
C_ALERT = (250, 191, 35, 255)
|
| 20 |
+
C_PANIC = (233, 68, 68, 255)
|
| 21 |
+
C_INCIDENT = (230, 60, 60, 96)
|
| 22 |
+
|
| 23 |
+
RUNNING = False
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class Person:
|
| 27 |
+
x: float; y: float; vx: float; vy: float
|
| 28 |
+
stress: float = 0.0; state: int = 0; evacuated: bool = False
|
| 29 |
+
def color(self): return [C_CALM, C_ALERT, C_PANIC][self.state]
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class ExitZone:
|
| 33 |
+
x: int; y: int; w: int = EXIT_W; h: int = EXIT_H
|
| 34 |
+
def rect(self): return (self.x, self.y, self.x + self.w, self.y + self.h)
|
| 35 |
+
|
| 36 |
+
@dataclass
|
| 37 |
+
class World:
|
| 38 |
+
width: int = W; height: int = H; room: Tuple[int,int,int,int] = ROOM
|
| 39 |
+
people: List[Person] = field(default_factory=list)
|
| 40 |
+
exits: List[ExitZone] = field(default_factory=list)
|
| 41 |
+
incident: Optional[Tuple[int,int,int]] = None
|
| 42 |
+
speed_scale: float = 1.0; cohesion: float = 0.3; separation: float = 0.6
|
| 43 |
+
panic_spread: float = 2.0; personal_space: float = 22.0
|
| 44 |
+
|
| 45 |
+
def step(self, dt: float):
|
| 46 |
+
def nearest_exit_vec(px, py):
|
| 47 |
+
best=None; best_d2=1e12
|
| 48 |
+
for ex in self.exits:
|
| 49 |
+
cx, cy = ex.x + ex.w/2, ex.y + ex.h/2
|
| 50 |
+
dx, dy = cx - px, cy - py
|
| 51 |
+
d2 = dx*dx + dy*dy
|
| 52 |
+
if d2 < best_d2: best_d2, best = d2, (dx, dy)
|
| 53 |
+
return best or (0.0, 0.0)
|
| 54 |
+
|
| 55 |
+
for p in self.people:
|
| 56 |
+
if p.evacuated: continue
|
| 57 |
+
gx, gy = nearest_exit_vec(p.x, p.y)
|
| 58 |
+
p.vx += gx*0.002; p.vy += gy*0.002
|
| 59 |
+
v = math.hypot(p.vx, p.vy)
|
| 60 |
+
vmax = MAX_SPEED_BASE*self.speed_scale
|
| 61 |
+
if v > vmax: p.vx, p.vy = p.vx/v*vmax, p.vy/v*vmax
|
| 62 |
+
p.x += p.vx*dt; p.y += p.vy*dt
|
| 63 |
+
for ex in self.exits:
|
| 64 |
+
x0, y0, x1, y1 = ex.rect()
|
| 65 |
+
if x0 <= p.x <= x1 and y0 <= p.y <= y1:
|
| 66 |
+
p.evacuated = True
|
| 67 |
+
|
| 68 |
+
def render(self):
|
| 69 |
+
img = Image.new("RGBA", (self.width, self.height), C_BG)
|
| 70 |
+
d = ImageDraw.Draw(img, "RGBA")
|
| 71 |
+
d.rectangle(self.room, fill=C_ROOM, outline=C_BORDER, width=3)
|
| 72 |
+
for ex in self.exits:
|
| 73 |
+
d.rectangle(ex.rect(), fill=C_EXIT)
|
| 74 |
+
d.text((ex.x+6, ex.y+ex.h/2-7), "Exit", fill=(255,255,255,255))
|
| 75 |
+
for p in self.people:
|
| 76 |
+
if not p.evacuated:
|
| 77 |
+
d.ellipse((p.x-4, p.y-4, p.x+4, p.y+4), fill=p.color())
|
| 78 |
+
return img
|
| 79 |
+
|
| 80 |
+
def make_world(n_people=200):
|
| 81 |
+
w = World()
|
| 82 |
+
ex1 = ExitZone(x=ROOM[2]-8-EXIT_W, y=int(ROOM[1]+70))
|
| 83 |
+
ex2 = ExitZone(x=ROOM[2]-8-EXIT_W, y=int(ROOM[3]-70-EXIT_H))
|
| 84 |
+
w.exits = [ex1, ex2]
|
| 85 |
+
for _ in range(n_people):
|
| 86 |
+
w.people.append(Person(random.randint(100, 700), random.randint(100, 400), 0, 0))
|
| 87 |
+
return w
|
| 88 |
+
|
| 89 |
+
def reset_fn(n_people):
|
| 90 |
+
w = make_world(int(n_people))
|
| 91 |
+
return w.render(), w
|
| 92 |
+
|
| 93 |
+
def start_fn(world):
|
| 94 |
+
global RUNNING; RUNNING = True
|
| 95 |
+
while RUNNING:
|
| 96 |
+
world.step(DT)
|
| 97 |
+
yield world.render(), world
|
| 98 |
+
time.sleep(DT)
|
| 99 |
+
|
| 100 |
+
def pause_fn():
|
| 101 |
+
global RUNNING; RUNNING = False
|
| 102 |
+
return gr.update(), gr.update()
|
| 103 |
+
|
| 104 |
+
with gr.Blocks(title="Behavioral ML in Virtual Crowds") as demo:
|
| 105 |
+
gr.Markdown("## Behavioral ML in Virtual Crowds · eenvoudige simulatie")
|
| 106 |
+
img = gr.Image(label="Simulatie")
|
| 107 |
+
n_people = gr.Slider(10, 500, value=200, step=10, label="Aantal mensen")
|
| 108 |
+
start_btn = gr.Button("▶️ Start")
|
| 109 |
+
pause_btn = gr.Button("⏸️ Pauze")
|
| 110 |
+
reset_btn = gr.Button("🔁 Reset")
|
| 111 |
+
state = gr.State()
|
| 112 |
+
reset_btn.click(reset_fn, [n_people], [img, state])
|
| 113 |
+
start_btn.click(start_fn, [state], [img, state])
|
| 114 |
+
pause_btn.click(pause_fn, outputs=[img, state])
|
| 115 |
+
|
| 116 |
+
if __name__ == "__main__":
|
| 117 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=5.0.0
|
| 2 |
+
pillow
|