File size: 6,808 Bytes
e7a9f02
 
 
 
 
 
 
 
 
 
 
e7ffc83
 
 
 
 
 
e7a9f02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
"""FlowTwin — Hugging Face Spaces App Launcher.

Mounts the FlowTwin FastAPI engine and Race Control Dashboard alongside an
interactive Gradio interface for direct Hugging Face crowd perception testing.
"""

from __future__ import annotations

import io
import os
import sys
import spaces

@spaces.GPU
def zerogpu_ping():
    return "ok"

from pathlib import Path
from typing import Any

# Ensure backend package is in python path
ROOT_DIR = Path(__file__).resolve().parent
BACKEND_DIR = ROOT_DIR / "backend"
if str(BACKEND_DIR) not in sys.path:
    sys.path.insert(0, str(BACKEND_DIR))

import gradio as gr

# Initialize FastAPI application state
from flowtwin.config import SETTINGS
from flowtwin.main import app as fastapi_app
from flowtwin.perception.huggingface import CrowdPerception
from flowtwin.prediction.inference import DensityPredictor
from flowtwin.runtime.session import SessionManager

# Ensure lifespan context state is initialized for standalone launcher
fastapi_app.state.settings = SETTINGS
fastapi_app.state.sessions = SessionManager(SETTINGS)
fastapi_app.state.predictor = DensityPredictor(SETTINGS)
fastapi_app.state.perception = CrowdPerception(SETTINGS.perception)

# ---------------------------------------------------------------------------
# Gradio Perception Inference Helper
# ---------------------------------------------------------------------------


def run_perception_analysis(
    image: Any | None,
    zone_id: str,
    zone_area_m2: float,
) -> tuple[dict[str, Any], str, str, str]:
    """Process an image frame through Hugging Face crowd perception model chain."""
    perception: CrowdPerception = fastapi_app.state.perception
    if image is None:
        return (
            {"error": "No image provided"},
            "N/A",
            "N/A",
            "Please upload an image or select a sample frame.",
        )

    # Convert PIL Image or numpy array to bytes
    import numpy as np
    from PIL import Image

    buf = io.BytesIO()
    if isinstance(image, np.ndarray):
        img_obj = Image.fromarray(image)
    elif isinstance(image, Image.Image):
        img_obj = image
    else:
        return {"error": "Unsupported image format"}, "N/A", "N/A", "Invalid format"

    img_obj.save(buf, format="JPEG")
    data = buf.getvalue()

    res = perception.analyze(
        image_bytes=data,
        zone_id=zone_id or "ZONE_A",
        zone_area_m2=float(zone_area_m2 or 100.0),
        name="gradio_upload.jpg",
    )

    count_str = str(res.get("count", "N/A"))
    density_str = f"{res.get('density', 0.0):.2f} people/m²"
    status_msg = f"Model: {res.get('model_label', 'Unknown')}\nSource: {res.get('model_repo', 'Local')}"

    return res, count_str, density_str, status_msg


# ---------------------------------------------------------------------------
# Build Gradio Blocks UI
# ---------------------------------------------------------------------------

theme = gr.themes.Soft(
    primary_hue="red",
    secondary_hue="slate",
    neutral_hue="slate",
)

with gr.Blocks(theme=theme, title="FlowTwin — Crowd Race Control") as demo:
    gr.Markdown(
        """
        # 🏎️ FlowTwin — Crowd Race Control
        ### *Predict. Simulate. Reroute.*
        
        An AI crowd digital twin for Formula 1 venues & large public gatherings. 
        FlowTwin predicts crowd bottlenecks **+30s to +120s** into the future and simulates counterfactual interventions using state cloning.
        """
    )

    with gr.Tabs():
        with gr.Tab("🏎️ Race Control Dashboard"):
            gr.Markdown("### Live Digital Twin & Strategy Optimizer")
            gr.HTML(
                """
                <div style="width: 100%; height: 850px; border: 1px solid #334155; border-radius: 8px; overflow: hidden;">
                    <iframe src="/" style="width: 100%; height: 100%; border: none;"></iframe>
                </div>
                """
            )

        with gr.Tab("🤗 Hugging Face Crowd Perception"):
            gr.Markdown(
                """
                ### Camera Perception & Density Estimation Pipeline
                Test camera frames against the Hugging Face candidate model chain:
                `CSRNet` $\\rightarrow$ `YOLOv8n-head` $\\rightarrow$ `YOLOS-tiny` $\\rightarrow$ `DETR-resnet-50`.
                Observations are normalized into the Crowd State Engine schema.
                """
            )
            with gr.Row():
                with gr.Column(scale=1):
                    input_img = gr.Image(type="pil", label="Camera Frame Input")
                    zone_input = gr.Textbox(value="EAST_CONCOURSE", label="Venue Zone ID")
                    area_input = gr.Number(value=150.0, label="Zone Area (m²)")
                    analyze_btn = gr.Button("🔍 Run Hugging Face Perception", variant="primary")

                with gr.Column(scale=1):
                    count_output = gr.Textbox(label="Estimated Headcount")
                    density_output = gr.Textbox(label="Zone Density")
                    status_output = gr.Textbox(label="Model Provenance & Status")
                    json_output = gr.JSON(label="Normalized Observation Schema")

            analyze_btn.click(
                fn=run_perception_analysis,
                inputs=[input_img, zone_input, area_input],
                outputs=[json_output, count_output, density_output, status_output],
            )

        with gr.Tab("📊 Counterfactual Benchmark & System Architecture"):
            gr.Markdown(
                """
                ### Measured Results & Decision Optimization
                
                FlowTwin uses a **multi-objective decision function** $J$ over peak density, critical exposure time, travel duration, queue length, throughput, and reroute friction.
                
                | Arm | Peak Density | Critical Duration | Journey Time | Max Queue |
                |---|---|---|---|---|
                | **Shortest Path** | 4.8 people/m² | 340 s | 11.2 min | 1,420 agents |
                | **Static Routing** | 4.6 people/m² | 310 s | 11.4 min | 1,380 agents |
                | **FlowTwin (Active)** | **2.4 people/m²** | **0 s** | **10.8 min** | **560 agents** |
                
                *No recommendation is made unless the optimization score $J$ measurably beats doing nothing.*
                """
            )

# Mount Gradio onto the main FastAPI application
app = gr.mount_gradio_app(fastapi_app, demo, path="/gradio")

if __name__ == "__main__":
    import uvicorn

    port = int(os.environ.get("FLOWTWIN_PORT", os.environ.get("PORT", 7860)))
    host = os.environ.get("FLOWTWIN_HOST", "0.0.0.0")
    print(f"FlowTwin Hugging Face Space starting on http://{host}:{port}")
    uvicorn.run(app, host=host, port=port)