Deploy REAL original app
Browse files- .gitattributes +0 -11
- Dockerfile +2 -20
- README.md +279 -0
- data/__init__.py +87 -0
- data/accident_schema.json +49 -0
- data/osm_extractor.py +409 -0
- data/processed/enhanced_synthetic_accidents_20260104_094620.csv +0 -0
- data/processed/synthetic_accidents.json +0 -0
- data/processed/synthetic_accidents_features.npz +3 -0
- data/processed/synthetic_accidents_training.json +0 -0
- data/processed/synthetic_accidents_training_features.npz +3 -0
- data/schema.py +416 -0
- data/synthetic_dataset_generator.py +967 -0
.gitattributes
CHANGED
|
@@ -1,16 +1,5 @@
|
|
| 1 |
-
# Git LFS Configuration for CrashLens
|
| 2 |
-
# Large File Storage for model files and media
|
| 3 |
-
# AI Model Files
|
| 4 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
# Data Files
|
| 10 |
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
# Video/Animation Files
|
| 13 |
*.gif filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
# Documentation
|
| 16 |
*.pdf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
*.npz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 4 |
*.gif filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 5 |
*.pdf filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
CHANGED
|
@@ -2,32 +2,14 @@ FROM python:3.9-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
|
| 6 |
-
RUN apt-get update && apt-get install -y \
|
| 7 |
-
build-essential \
|
| 8 |
-
curl \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
-
# Copy requirements
|
| 12 |
COPY requirements.txt .
|
| 13 |
-
|
| 14 |
-
# Install Python packages
|
| 15 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
|
| 17 |
-
# Copy all application files
|
| 18 |
COPY . .
|
| 19 |
-
|
| 20 |
-
# Create output directories
|
| 21 |
RUN mkdir -p output/reports logs
|
| 22 |
|
| 23 |
-
# Expose port 7860 for Hugging Face Spaces
|
| 24 |
EXPOSE 7860
|
| 25 |
|
| 26 |
-
|
| 27 |
-
ENV STREAMLIT_SERVER_PORT=7860
|
| 28 |
-
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
|
| 29 |
-
ENV STREAMLIT_SERVER_HEADLESS=true
|
| 30 |
-
ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
|
| 31 |
-
|
| 32 |
-
# Run the app
|
| 33 |
-
CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
RUN apt-get update && apt-get install -y build-essential curl && rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
|
|
|
| 7 |
COPY requirements.txt .
|
|
|
|
|
|
|
| 8 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 9 |
|
|
|
|
| 10 |
COPY . .
|
|
|
|
|
|
|
| 11 |
RUN mkdir -p output/reports logs
|
| 12 |
|
|
|
|
| 13 |
EXPOSE 7860
|
| 14 |
|
| 15 |
+
CMD streamlit run app.py --server.port=7860 --server.address=0.0.0.0 --server.headless=true --server.fileWatcherType=none --browser.gatherUsageStats=false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚗 Traffic Accident Reconstruction System
|
| 2 |
+
|
| 3 |
+
## AI-Powered Analysis using Huawei MindSpore
|
| 4 |
+
|
| 5 |
+
> **Huawei AI Innovation Challenge 2026**
|
| 6 |
+
> AI Research Team
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 📋 Project Overview
|
| 11 |
+
|
| 12 |
+
This system is an intelligent traffic accident reconstruction tool that helps traffic authorities understand accidents in a clearer and fairer manner. It reconstructs accidents at their real geographic locations using 2D traffic simulation and AI-powered scenario generation.
|
| 13 |
+
|
| 14 |
+
### Key Features
|
| 15 |
+
|
| 16 |
+
- **Real Map Integration**: Uses OpenStreetMap to display actual road layouts
|
| 17 |
+
- **AI Scenario Generation**: MindSpore-powered analysis generates multiple accident scenarios
|
| 18 |
+
- **Probability Analysis**: Each scenario includes probability scores and contributing factors
|
| 19 |
+
- **2D Traffic Simulation**: SUMO integration for realistic accident reconstruction
|
| 20 |
+
- **Comprehensive Reports**: PDF and HTML report generation
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 🛠️ Technology Stack
|
| 25 |
+
|
| 26 |
+
| Component | Technology |
|
| 27 |
+
|-----------|------------|
|
| 28 |
+
| **AI Framework** | Huawei MindSpore |
|
| 29 |
+
| **Web Interface** | Streamlit |
|
| 30 |
+
| **Traffic Simulation** | SUMO |
|
| 31 |
+
| **Map Data** | OpenStreetMap |
|
| 32 |
+
| **Data Processing** | NumPy, Pandas |
|
| 33 |
+
| **Visualization** | Plotly, Folium |
|
| 34 |
+
| **Report Generation** | ReportLab, Jinja2 |
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## 📁 Project Structure
|
| 39 |
+
|
| 40 |
+
```
|
| 41 |
+
traffic_accident_analyzer/
|
| 42 |
+
├── app.py # Main Streamlit application
|
| 43 |
+
├── config.py # Configuration settings
|
| 44 |
+
├── requirements.txt # Python dependencies
|
| 45 |
+
├── setup.sh # Setup script
|
| 46 |
+
├── README.md # This file
|
| 47 |
+
│
|
| 48 |
+
├── ui/ # User interface components
|
| 49 |
+
│ ├── __init__.py
|
| 50 |
+
│ ├── components.py # Reusable UI components
|
| 51 |
+
│ ├── map_viewer.py # Map display and path drawing
|
| 52 |
+
│ ├── vehicle_input.py # Vehicle data input forms
|
| 53 |
+
│ └── results_display.py # Results visualization
|
| 54 |
+
│
|
| 55 |
+
├── analysis/ # Analysis modules
|
| 56 |
+
│ ├── __init__.py
|
| 57 |
+
│ ├── scenario_analyzer.py # AI scenario generation
|
| 58 |
+
│ └── report_generator.py # Report generation
|
| 59 |
+
│
|
| 60 |
+
├── models/ # MindSpore models
|
| 61 |
+
│ ├── __init__.py
|
| 62 |
+
│ ├── mindspore_model.py # MindSpore neural network
|
| 63 |
+
│ └── trained/ # Saved model weights
|
| 64 |
+
│
|
| 65 |
+
├── simulation/ # SUMO simulation
|
| 66 |
+
│ ├── __init__.py
|
| 67 |
+
│ ├── sumo_interface.py # SUMO integration
|
| 68 |
+
│ ├── networks/ # SUMO network files
|
| 69 |
+
│ └── output/ # Simulation outputs
|
| 70 |
+
│
|
| 71 |
+
├── data/ # Data files
|
| 72 |
+
│ ├── raw/ # Raw data
|
| 73 |
+
│ ├── processed/ # Processed data
|
| 74 |
+
│ └── osm/ # OpenStreetMap data
|
| 75 |
+
│
|
| 76 |
+
├── output/ # Output files
|
| 77 |
+
│ ├── reports/ # Generated reports
|
| 78 |
+
│ └── visualizations/ # Generated visualizations
|
| 79 |
+
│
|
| 80 |
+
└── logs/ # Application logs
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 🚀 Quick Start
|
| 86 |
+
|
| 87 |
+
### Prerequisites
|
| 88 |
+
|
| 89 |
+
- Python 3.8 or higher
|
| 90 |
+
- SUMO (for traffic simulation)
|
| 91 |
+
- Git
|
| 92 |
+
|
| 93 |
+
### Installation
|
| 94 |
+
|
| 95 |
+
1. **Clone or download the project**
|
| 96 |
+
```bash
|
| 97 |
+
cd traffic_accident_analyzer
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
2. **Run the setup script**
|
| 101 |
+
```bash
|
| 102 |
+
chmod +x setup.sh
|
| 103 |
+
./setup.sh
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
3. **Or install manually**
|
| 107 |
+
```bash
|
| 108 |
+
python3 -m venv venv
|
| 109 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 110 |
+
pip install -r requirements.txt
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
4. **Run the application**
|
| 114 |
+
```bash
|
| 115 |
+
streamlit run app.py
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
5. **Open in browser**
|
| 119 |
+
- Navigate to `http://localhost:8501`
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## 📖 How to Use
|
| 124 |
+
|
| 125 |
+
### Step 1: Select Accident Location
|
| 126 |
+
- Choose a preset location or enter custom coordinates
|
| 127 |
+
- The system displays the real road layout from OpenStreetMap
|
| 128 |
+
|
| 129 |
+
### Step 2: Enter Vehicle 1 Information
|
| 130 |
+
- Select vehicle type, speed, and direction
|
| 131 |
+
- Draw the vehicle's path on the map
|
| 132 |
+
- Add driver's description
|
| 133 |
+
|
| 134 |
+
### Step 3: Enter Vehicle 2 Information
|
| 135 |
+
- Repeat for the second vehicle
|
| 136 |
+
- Both paths are displayed together
|
| 137 |
+
|
| 138 |
+
### Step 4: Run AI Analysis
|
| 139 |
+
- Click "Run AI Analysis" to generate scenarios
|
| 140 |
+
- MindSpore AI processes the data
|
| 141 |
+
|
| 142 |
+
### Step 5: View Results
|
| 143 |
+
- See multiple generated scenarios with probabilities
|
| 144 |
+
- View detailed metrics and contributing factors
|
| 145 |
+
- Generate PDF or HTML reports
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## 🎯 System Workflow
|
| 150 |
+
|
| 151 |
+
```
|
| 152 |
+
┌─────────────────┐
|
| 153 |
+
│ User Input │
|
| 154 |
+
│ (Location, │
|
| 155 |
+
│ Vehicles) │
|
| 156 |
+
└────────┬────────┘
|
| 157 |
+
│
|
| 158 |
+
▼
|
| 159 |
+
┌─────────────────┐
|
| 160 |
+
│ OpenStreetMap │
|
| 161 |
+
│ (Road Layout) │
|
| 162 |
+
└────────┬────────┘
|
| 163 |
+
│
|
| 164 |
+
▼
|
| 165 |
+
┌─────────────────┐
|
| 166 |
+
│ Feature │
|
| 167 |
+
│ Extraction │
|
| 168 |
+
└────────┬────────┘
|
| 169 |
+
│
|
| 170 |
+
▼
|
| 171 |
+
┌─────────────────┐
|
| 172 |
+
│ MindSpore AI │
|
| 173 |
+
│ (Scenario │
|
| 174 |
+
│ Generation) │
|
| 175 |
+
└────────┬────────┘
|
| 176 |
+
│
|
| 177 |
+
▼
|
| 178 |
+
┌─────────────────┐
|
| 179 |
+
│ SUMO │
|
| 180 |
+
│ (Simulation) │
|
| 181 |
+
└────────┬────────┘
|
| 182 |
+
│
|
| 183 |
+
▼
|
| 184 |
+
┌─────────────────┐
|
| 185 |
+
│ Analysis & │
|
| 186 |
+
│ Report │
|
| 187 |
+
└─────────────────┘
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## 🧠 AI Model Details
|
| 193 |
+
|
| 194 |
+
### Input Features (24 features)
|
| 195 |
+
- Vehicle speeds and dimensions
|
| 196 |
+
- Travel directions and angles
|
| 197 |
+
- Weather and road conditions
|
| 198 |
+
- Path overlap metrics
|
| 199 |
+
- Action risk factors
|
| 200 |
+
|
| 201 |
+
### Output
|
| 202 |
+
- 5 most probable accident scenarios
|
| 203 |
+
- Probability scores for each
|
| 204 |
+
- Contributing factors
|
| 205 |
+
- Collision point estimation
|
| 206 |
+
|
| 207 |
+
### Model Architecture
|
| 208 |
+
- Multi-layer neural network
|
| 209 |
+
- Built with MindSpore framework
|
| 210 |
+
- Trained on synthetic accident data
|
| 211 |
+
|
| 212 |
+
---
|
| 213 |
+
|
| 214 |
+
## 📊 Analysis Metrics
|
| 215 |
+
|
| 216 |
+
| Metric | Weight | Description |
|
| 217 |
+
|--------|--------|-------------|
|
| 218 |
+
| Collision Probability | 35% | Likelihood based on trajectories |
|
| 219 |
+
| Path Overlap | 25% | Degree of path intersection |
|
| 220 |
+
| Speed Differential | 20% | Speed difference at collision |
|
| 221 |
+
| Timing Analysis | 20% | Time gap at conflict point |
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## 🌍 Case Study Location
|
| 226 |
+
|
| 227 |
+
**دوار السيف - Seef District Roundabout, Bahrain**
|
| 228 |
+
|
| 229 |
+
- **City:** Manama, Bahrain
|
| 230 |
+
- **Latitude:** 26.2397
|
| 231 |
+
- **Longitude:** 50.5369
|
| 232 |
+
- **Description:** Major roundabout in the Seef commercial district, near City Centre Bahrain mall
|
| 233 |
+
|
| 234 |
+
---
|
| 235 |
+
|
| 236 |
+
## 📄 Report Contents
|
| 237 |
+
|
| 238 |
+
- Executive Summary
|
| 239 |
+
- Accident Details
|
| 240 |
+
- Vehicle Information
|
| 241 |
+
- AI-Generated Scenarios
|
| 242 |
+
- Probability Analysis
|
| 243 |
+
- Contributing Factors
|
| 244 |
+
- Preliminary Fault Assessment
|
| 245 |
+
- Timeline Reconstruction
|
| 246 |
+
- Recommendations
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## ⚠️ Disclaimer
|
| 251 |
+
|
| 252 |
+
This system provides AI-generated analysis for reference purposes only. Final accident determination should be made by qualified traffic authorities based on comprehensive investigation.
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
## 🏆 Competition Information
|
| 257 |
+
|
| 258 |
+
**Huawei AI Innovation Challenge 2026**
|
| 259 |
+
|
| 260 |
+
- **Track**: Innovation Track
|
| 261 |
+
- **Theme**: MindSpore AI Applications
|
| 262 |
+
- **Institution**: AI Research Team
|
| 263 |
+
- **Country**: Saudi Arabia
|
| 264 |
+
|
| 265 |
+
---
|
| 266 |
+
|
| 267 |
+
## 📧 Contact
|
| 268 |
+
|
| 269 |
+
For questions or support regarding this project, please contact the project team.
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
## 📜 License
|
| 274 |
+
|
| 275 |
+
This project is developed for the Huawei AI Innovation Challenge 2026.
|
| 276 |
+
|
| 277 |
+
---
|
| 278 |
+
|
| 279 |
+
**Built with ❤️ using Huawei MindSpore**
|
data/__init__.py
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data Package
|
| 3 |
+
=============
|
| 4 |
+
Data handling, schema definitions, and dataset generation for
|
| 5 |
+
the Traffic Accident Reconstruction System.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .schema import (
|
| 9 |
+
# Enums
|
| 10 |
+
VehicleType,
|
| 11 |
+
Direction,
|
| 12 |
+
VehicleAction,
|
| 13 |
+
WeatherCondition,
|
| 14 |
+
RoadCondition,
|
| 15 |
+
AccidentType,
|
| 16 |
+
Severity,
|
| 17 |
+
RoadType,
|
| 18 |
+
|
| 19 |
+
# Data classes
|
| 20 |
+
Location,
|
| 21 |
+
Conditions,
|
| 22 |
+
VehicleData,
|
| 23 |
+
AccidentDetails,
|
| 24 |
+
ScenarioMetrics,
|
| 25 |
+
Scenario,
|
| 26 |
+
FaultAssessment,
|
| 27 |
+
TimelineEvent,
|
| 28 |
+
AccidentRecord,
|
| 29 |
+
AnalysisResult,
|
| 30 |
+
|
| 31 |
+
# Feature schema
|
| 32 |
+
FEATURE_SCHEMA,
|
| 33 |
+
|
| 34 |
+
# Validation functions
|
| 35 |
+
validate_speed,
|
| 36 |
+
validate_coordinates,
|
| 37 |
+
validate_path,
|
| 38 |
+
validate_probability
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
from .synthetic_dataset_generator import (
|
| 42 |
+
generate_dataset,
|
| 43 |
+
generate_single_accident,
|
| 44 |
+
generate_training_features,
|
| 45 |
+
save_dataset,
|
| 46 |
+
print_dataset_statistics
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
__all__ = [
|
| 50 |
+
# Enums
|
| 51 |
+
'VehicleType',
|
| 52 |
+
'Direction',
|
| 53 |
+
'VehicleAction',
|
| 54 |
+
'WeatherCondition',
|
| 55 |
+
'RoadCondition',
|
| 56 |
+
'AccidentType',
|
| 57 |
+
'Severity',
|
| 58 |
+
'RoadType',
|
| 59 |
+
|
| 60 |
+
# Data classes
|
| 61 |
+
'Location',
|
| 62 |
+
'Conditions',
|
| 63 |
+
'VehicleData',
|
| 64 |
+
'AccidentDetails',
|
| 65 |
+
'ScenarioMetrics',
|
| 66 |
+
'Scenario',
|
| 67 |
+
'FaultAssessment',
|
| 68 |
+
'TimelineEvent',
|
| 69 |
+
'AccidentRecord',
|
| 70 |
+
'AnalysisResult',
|
| 71 |
+
|
| 72 |
+
# Feature schema
|
| 73 |
+
'FEATURE_SCHEMA',
|
| 74 |
+
|
| 75 |
+
# Validation
|
| 76 |
+
'validate_speed',
|
| 77 |
+
'validate_coordinates',
|
| 78 |
+
'validate_path',
|
| 79 |
+
'validate_probability',
|
| 80 |
+
|
| 81 |
+
# Dataset generation
|
| 82 |
+
'generate_dataset',
|
| 83 |
+
'generate_single_accident',
|
| 84 |
+
'generate_training_features',
|
| 85 |
+
'save_dataset',
|
| 86 |
+
'print_dataset_statistics'
|
| 87 |
+
]
|
data/accident_schema.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"accident_id": "string",
|
| 3 |
+
"timestamp": "datetime",
|
| 4 |
+
"location": {
|
| 5 |
+
"name": "string",
|
| 6 |
+
"latitude": "float",
|
| 7 |
+
"longitude": "float",
|
| 8 |
+
"road_type": "string"
|
| 9 |
+
},
|
| 10 |
+
"conditions": {
|
| 11 |
+
"weather": "string",
|
| 12 |
+
"road_condition": "string",
|
| 13 |
+
"visibility": "float",
|
| 14 |
+
"lighting": "string"
|
| 15 |
+
},
|
| 16 |
+
"vehicle_1": {
|
| 17 |
+
"type": "string",
|
| 18 |
+
"speed_kmh": "float",
|
| 19 |
+
"direction": "string",
|
| 20 |
+
"direction_angle": "float",
|
| 21 |
+
"action": "string",
|
| 22 |
+
"braking": "boolean",
|
| 23 |
+
"signaling": "boolean",
|
| 24 |
+
"path": "list[tuple]"
|
| 25 |
+
},
|
| 26 |
+
"vehicle_2": {
|
| 27 |
+
"type": "string",
|
| 28 |
+
"speed_kmh": "float",
|
| 29 |
+
"direction": "string",
|
| 30 |
+
"direction_angle": "float",
|
| 31 |
+
"action": "string",
|
| 32 |
+
"braking": "boolean",
|
| 33 |
+
"signaling": "boolean",
|
| 34 |
+
"path": "list[tuple]"
|
| 35 |
+
},
|
| 36 |
+
"accident_details": {
|
| 37 |
+
"type": "string",
|
| 38 |
+
"severity": "string",
|
| 39 |
+
"collision_angle": "float",
|
| 40 |
+
"collision_point": "tuple",
|
| 41 |
+
"contributing_factors": "list[string]",
|
| 42 |
+
"fault_vehicle": "int"
|
| 43 |
+
},
|
| 44 |
+
"outcomes": {
|
| 45 |
+
"scenario_probability": "float",
|
| 46 |
+
"damage_estimate": "string",
|
| 47 |
+
"injuries": "boolean"
|
| 48 |
+
}
|
| 49 |
+
}
|
data/osm_extractor.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
OpenStreetMap Data Extractor
|
| 3 |
+
============================
|
| 4 |
+
Downloads and processes real road network data from OpenStreetMap
|
| 5 |
+
for use in accident reconstruction simulation.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Dict, List, Tuple, Optional
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
import osmnx as ox
|
| 15 |
+
OSMNX_AVAILABLE = True
|
| 16 |
+
except ImportError:
|
| 17 |
+
OSMNX_AVAILABLE = False
|
| 18 |
+
print("Warning: osmnx not installed. Install with: pip install osmnx")
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
import folium
|
| 22 |
+
FOLIUM_AVAILABLE = True
|
| 23 |
+
except ImportError:
|
| 24 |
+
FOLIUM_AVAILABLE = False
|
| 25 |
+
|
| 26 |
+
import sys
|
| 27 |
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 28 |
+
|
| 29 |
+
from config import CASE_STUDY_LOCATION, ALTERNATIVE_LOCATIONS, OSM_DATA_DIR
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def download_road_network(
|
| 33 |
+
latitude: float,
|
| 34 |
+
longitude: float,
|
| 35 |
+
radius: int = 200,
|
| 36 |
+
network_type: str = "drive",
|
| 37 |
+
save_path: Optional[str] = None
|
| 38 |
+
) -> Dict:
|
| 39 |
+
"""
|
| 40 |
+
Download road network from OpenStreetMap.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
latitude: Center latitude
|
| 44 |
+
longitude: Center longitude
|
| 45 |
+
radius: Radius in meters
|
| 46 |
+
network_type: Type of network ('drive', 'walk', 'bike', 'all')
|
| 47 |
+
save_path: Optional path to save the network
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
Dictionary containing network data
|
| 51 |
+
"""
|
| 52 |
+
if not OSMNX_AVAILABLE:
|
| 53 |
+
print("osmnx not available. Using fallback data.")
|
| 54 |
+
return create_fallback_network(latitude, longitude, radius)
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
# Configure osmnx
|
| 58 |
+
ox.settings.use_cache = True
|
| 59 |
+
ox.settings.log_console = True
|
| 60 |
+
|
| 61 |
+
# Download the road network
|
| 62 |
+
print(f"Downloading road network for ({latitude}, {longitude})...")
|
| 63 |
+
G = ox.graph_from_point(
|
| 64 |
+
(latitude, longitude),
|
| 65 |
+
dist=radius,
|
| 66 |
+
network_type=network_type,
|
| 67 |
+
simplify=True
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# Get nodes and edges
|
| 71 |
+
nodes, edges = ox.graph_to_gdfs(G)
|
| 72 |
+
|
| 73 |
+
# Convert to dictionary format
|
| 74 |
+
network_data = {
|
| 75 |
+
"center": {"lat": latitude, "lng": longitude},
|
| 76 |
+
"radius": radius,
|
| 77 |
+
"nodes": [],
|
| 78 |
+
"edges": [],
|
| 79 |
+
"bounds": {
|
| 80 |
+
"north": nodes.geometry.y.max(),
|
| 81 |
+
"south": nodes.geometry.y.min(),
|
| 82 |
+
"east": nodes.geometry.x.max(),
|
| 83 |
+
"west": nodes.geometry.x.min()
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
# Process nodes
|
| 88 |
+
for idx, row in nodes.iterrows():
|
| 89 |
+
network_data["nodes"].append({
|
| 90 |
+
"id": str(idx),
|
| 91 |
+
"lat": row.geometry.y,
|
| 92 |
+
"lng": row.geometry.x
|
| 93 |
+
})
|
| 94 |
+
|
| 95 |
+
# Process edges
|
| 96 |
+
for idx, row in edges.iterrows():
|
| 97 |
+
edge_data = {
|
| 98 |
+
"from": str(idx[0]),
|
| 99 |
+
"to": str(idx[1]),
|
| 100 |
+
"length": row.get("length", 0),
|
| 101 |
+
"name": row.get("name", "Unknown"),
|
| 102 |
+
"highway": row.get("highway", "unclassified"),
|
| 103 |
+
"lanes": row.get("lanes", 1),
|
| 104 |
+
"maxspeed": row.get("maxspeed", "50")
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
# Get geometry if available
|
| 108 |
+
if hasattr(row.geometry, 'coords'):
|
| 109 |
+
edge_data["geometry"] = list(row.geometry.coords)
|
| 110 |
+
|
| 111 |
+
network_data["edges"].append(edge_data)
|
| 112 |
+
|
| 113 |
+
# Save if path provided
|
| 114 |
+
if save_path:
|
| 115 |
+
save_path = Path(save_path)
|
| 116 |
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
| 117 |
+
|
| 118 |
+
with open(save_path, 'w') as f:
|
| 119 |
+
json.dump(network_data, f, indent=2)
|
| 120 |
+
|
| 121 |
+
# Also save as GraphML for SUMO conversion
|
| 122 |
+
graphml_path = save_path.with_suffix('.graphml')
|
| 123 |
+
ox.save_graphml(G, graphml_path)
|
| 124 |
+
|
| 125 |
+
print(f"Network saved to {save_path}")
|
| 126 |
+
print(f"GraphML saved to {graphml_path}")
|
| 127 |
+
|
| 128 |
+
return network_data
|
| 129 |
+
|
| 130 |
+
except Exception as e:
|
| 131 |
+
print(f"Error downloading network: {e}")
|
| 132 |
+
return create_fallback_network(latitude, longitude, radius)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def create_fallback_network(
|
| 136 |
+
latitude: float,
|
| 137 |
+
longitude: float,
|
| 138 |
+
radius: int = 200
|
| 139 |
+
) -> Dict:
|
| 140 |
+
"""
|
| 141 |
+
Create a fallback network when OSM download fails.
|
| 142 |
+
Creates a simple roundabout structure.
|
| 143 |
+
"""
|
| 144 |
+
import math
|
| 145 |
+
|
| 146 |
+
# Create a simple roundabout with 4 approaches
|
| 147 |
+
network_data = {
|
| 148 |
+
"center": {"lat": latitude, "lng": longitude},
|
| 149 |
+
"radius": radius,
|
| 150 |
+
"nodes": [],
|
| 151 |
+
"edges": [],
|
| 152 |
+
"bounds": {
|
| 153 |
+
"north": latitude + 0.002,
|
| 154 |
+
"south": latitude - 0.002,
|
| 155 |
+
"east": longitude + 0.002,
|
| 156 |
+
"west": longitude - 0.002
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
# Create roundabout nodes (8 points in a circle)
|
| 161 |
+
roundabout_radius = 0.0003 # Approximately 30 meters
|
| 162 |
+
num_points = 8
|
| 163 |
+
|
| 164 |
+
for i in range(num_points):
|
| 165 |
+
angle = (2 * math.pi * i) / num_points
|
| 166 |
+
lat = latitude + roundabout_radius * math.cos(angle)
|
| 167 |
+
lng = longitude + roundabout_radius * math.sin(angle)
|
| 168 |
+
|
| 169 |
+
network_data["nodes"].append({
|
| 170 |
+
"id": f"r{i}",
|
| 171 |
+
"lat": lat,
|
| 172 |
+
"lng": lng,
|
| 173 |
+
"type": "roundabout"
|
| 174 |
+
})
|
| 175 |
+
|
| 176 |
+
# Create approach nodes (4 directions)
|
| 177 |
+
approach_distance = 0.001 # Approximately 100 meters
|
| 178 |
+
approaches = [
|
| 179 |
+
("north", latitude + approach_distance, longitude),
|
| 180 |
+
("south", latitude - approach_distance, longitude),
|
| 181 |
+
("east", latitude, longitude + approach_distance),
|
| 182 |
+
("west", latitude, longitude - approach_distance)
|
| 183 |
+
]
|
| 184 |
+
|
| 185 |
+
for name, lat, lng in approaches:
|
| 186 |
+
network_data["nodes"].append({
|
| 187 |
+
"id": f"a_{name}",
|
| 188 |
+
"lat": lat,
|
| 189 |
+
"lng": lng,
|
| 190 |
+
"type": "approach"
|
| 191 |
+
})
|
| 192 |
+
|
| 193 |
+
# Create roundabout edges (circular)
|
| 194 |
+
for i in range(num_points):
|
| 195 |
+
next_i = (i + 1) % num_points
|
| 196 |
+
network_data["edges"].append({
|
| 197 |
+
"from": f"r{i}",
|
| 198 |
+
"to": f"r{next_i}",
|
| 199 |
+
"length": 20,
|
| 200 |
+
"name": "Roundabout",
|
| 201 |
+
"highway": "primary",
|
| 202 |
+
"lanes": 2
|
| 203 |
+
})
|
| 204 |
+
|
| 205 |
+
# Connect approaches to roundabout
|
| 206 |
+
approach_connections = [
|
| 207 |
+
("a_north", "r0", "r6"),
|
| 208 |
+
("a_east", "r2", "r0"),
|
| 209 |
+
("a_south", "r4", "r2"),
|
| 210 |
+
("a_west", "r6", "r4")
|
| 211 |
+
]
|
| 212 |
+
|
| 213 |
+
for approach, entry, exit in approach_connections:
|
| 214 |
+
# Entry edge
|
| 215 |
+
network_data["edges"].append({
|
| 216 |
+
"from": approach,
|
| 217 |
+
"to": entry,
|
| 218 |
+
"length": 80,
|
| 219 |
+
"name": "Approach Road",
|
| 220 |
+
"highway": "primary",
|
| 221 |
+
"lanes": 2
|
| 222 |
+
})
|
| 223 |
+
# Exit edge
|
| 224 |
+
network_data["edges"].append({
|
| 225 |
+
"from": exit,
|
| 226 |
+
"to": approach,
|
| 227 |
+
"length": 80,
|
| 228 |
+
"name": "Exit Road",
|
| 229 |
+
"highway": "primary",
|
| 230 |
+
"lanes": 2
|
| 231 |
+
})
|
| 232 |
+
|
| 233 |
+
return network_data
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def extract_intersection_data(
|
| 237 |
+
latitude: float,
|
| 238 |
+
longitude: float,
|
| 239 |
+
radius: int = 100
|
| 240 |
+
) -> Dict:
|
| 241 |
+
"""
|
| 242 |
+
Extract intersection-specific data from OSM.
|
| 243 |
+
"""
|
| 244 |
+
if not OSMNX_AVAILABLE:
|
| 245 |
+
return create_fallback_intersection(latitude, longitude)
|
| 246 |
+
|
| 247 |
+
try:
|
| 248 |
+
# Get features around the point
|
| 249 |
+
tags = {"highway": True}
|
| 250 |
+
gdf = ox.features_from_point((latitude, longitude), tags, dist=radius)
|
| 251 |
+
|
| 252 |
+
intersection_data = {
|
| 253 |
+
"center": {"lat": latitude, "lng": longitude},
|
| 254 |
+
"features": []
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
for idx, row in gdf.iterrows():
|
| 258 |
+
feature = {
|
| 259 |
+
"type": row.get("highway", "unknown"),
|
| 260 |
+
"name": row.get("name", "Unknown")
|
| 261 |
+
}
|
| 262 |
+
if hasattr(row.geometry, 'centroid'):
|
| 263 |
+
feature["lat"] = row.geometry.centroid.y
|
| 264 |
+
feature["lng"] = row.geometry.centroid.x
|
| 265 |
+
|
| 266 |
+
intersection_data["features"].append(feature)
|
| 267 |
+
|
| 268 |
+
return intersection_data
|
| 269 |
+
|
| 270 |
+
except Exception as e:
|
| 271 |
+
print(f"Error extracting intersection: {e}")
|
| 272 |
+
return create_fallback_intersection(latitude, longitude)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def create_fallback_intersection(latitude: float, longitude: float) -> Dict:
|
| 276 |
+
"""Create fallback intersection data."""
|
| 277 |
+
return {
|
| 278 |
+
"center": {"lat": latitude, "lng": longitude},
|
| 279 |
+
"type": "roundabout",
|
| 280 |
+
"approaches": 4,
|
| 281 |
+
"lanes": 2,
|
| 282 |
+
"features": [
|
| 283 |
+
{"type": "primary", "name": "Main Road North-South"},
|
| 284 |
+
{"type": "primary", "name": "Main Road East-West"}
|
| 285 |
+
]
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def create_location_map(
|
| 290 |
+
location: Dict,
|
| 291 |
+
network_data: Optional[Dict] = None,
|
| 292 |
+
save_path: Optional[str] = None
|
| 293 |
+
) -> 'folium.Map':
|
| 294 |
+
"""
|
| 295 |
+
Create an interactive Folium map for the location.
|
| 296 |
+
"""
|
| 297 |
+
if not FOLIUM_AVAILABLE:
|
| 298 |
+
print("Folium not available")
|
| 299 |
+
return None
|
| 300 |
+
|
| 301 |
+
# Create base map
|
| 302 |
+
m = folium.Map(
|
| 303 |
+
location=[location["latitude"], location["longitude"]],
|
| 304 |
+
zoom_start=17,
|
| 305 |
+
tiles="OpenStreetMap"
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
# Add center marker
|
| 309 |
+
folium.Marker(
|
| 310 |
+
location=[location["latitude"], location["longitude"]],
|
| 311 |
+
popup=location.get("name", "Location"),
|
| 312 |
+
icon=folium.Icon(color="red", icon="info-sign")
|
| 313 |
+
).add_to(m)
|
| 314 |
+
|
| 315 |
+
# Add radius circle
|
| 316 |
+
folium.Circle(
|
| 317 |
+
location=[location["latitude"], location["longitude"]],
|
| 318 |
+
radius=location.get("radius_meters", 200),
|
| 319 |
+
color="blue",
|
| 320 |
+
fill=True,
|
| 321 |
+
fill_opacity=0.1
|
| 322 |
+
).add_to(m)
|
| 323 |
+
|
| 324 |
+
# Add network data if available
|
| 325 |
+
if network_data:
|
| 326 |
+
# Add nodes
|
| 327 |
+
for node in network_data.get("nodes", []):
|
| 328 |
+
folium.CircleMarker(
|
| 329 |
+
location=[node["lat"], node["lng"]],
|
| 330 |
+
radius=3,
|
| 331 |
+
color="green",
|
| 332 |
+
fill=True
|
| 333 |
+
).add_to(m)
|
| 334 |
+
|
| 335 |
+
# Add edges
|
| 336 |
+
for edge in network_data.get("edges", []):
|
| 337 |
+
if "geometry" in edge:
|
| 338 |
+
# Use actual geometry
|
| 339 |
+
coords = [(c[1], c[0]) for c in edge["geometry"]]
|
| 340 |
+
folium.PolyLine(
|
| 341 |
+
locations=coords,
|
| 342 |
+
color="gray",
|
| 343 |
+
weight=2
|
| 344 |
+
).add_to(m)
|
| 345 |
+
|
| 346 |
+
# Save if path provided
|
| 347 |
+
if save_path:
|
| 348 |
+
m.save(save_path)
|
| 349 |
+
print(f"Map saved to {save_path}")
|
| 350 |
+
|
| 351 |
+
return m
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def download_all_locations():
|
| 355 |
+
"""Download network data for all configured locations."""
|
| 356 |
+
|
| 357 |
+
# Create output directory
|
| 358 |
+
OSM_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
| 359 |
+
|
| 360 |
+
# Download primary location
|
| 361 |
+
print("\n" + "="*50)
|
| 362 |
+
print("Downloading primary location...")
|
| 363 |
+
print("="*50)
|
| 364 |
+
|
| 365 |
+
primary_data = download_road_network(
|
| 366 |
+
latitude=CASE_STUDY_LOCATION["latitude"],
|
| 367 |
+
longitude=CASE_STUDY_LOCATION["longitude"],
|
| 368 |
+
radius=CASE_STUDY_LOCATION["radius_meters"],
|
| 369 |
+
save_path=OSM_DATA_DIR / "primary_location.json"
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
# Create map for primary location
|
| 373 |
+
create_location_map(
|
| 374 |
+
CASE_STUDY_LOCATION,
|
| 375 |
+
primary_data,
|
| 376 |
+
save_path=str(OSM_DATA_DIR / "primary_location_map.html")
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
# Download alternative locations
|
| 380 |
+
for loc_key, loc_data in ALTERNATIVE_LOCATIONS.items():
|
| 381 |
+
print(f"\n" + "="*50)
|
| 382 |
+
print(f"Downloading {loc_data['name']}...")
|
| 383 |
+
print("="*50)
|
| 384 |
+
|
| 385 |
+
network_data = download_road_network(
|
| 386 |
+
latitude=loc_data["latitude"],
|
| 387 |
+
longitude=loc_data["longitude"],
|
| 388 |
+
radius=loc_data["radius_meters"],
|
| 389 |
+
save_path=OSM_DATA_DIR / f"{loc_key}.json"
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
create_location_map(
|
| 393 |
+
{
|
| 394 |
+
"latitude": loc_data["latitude"],
|
| 395 |
+
"longitude": loc_data["longitude"],
|
| 396 |
+
"radius_meters": loc_data["radius_meters"],
|
| 397 |
+
"name": loc_data["name"]
|
| 398 |
+
},
|
| 399 |
+
network_data,
|
| 400 |
+
save_path=str(OSM_DATA_DIR / f"{loc_key}_map.html")
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
print("\n" + "="*50)
|
| 404 |
+
print("All locations downloaded successfully!")
|
| 405 |
+
print("="*50)
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
if __name__ == "__main__":
|
| 409 |
+
download_all_locations()
|
data/processed/enhanced_synthetic_accidents_20260104_094620.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/processed/synthetic_accidents.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/processed/synthetic_accidents_features.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:27efbff25a33aa63bf871e238262e8382c494a77ee77f6ae0ad0db3bb6c42b32
|
| 3 |
+
size 48490
|
data/processed/synthetic_accidents_training.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/processed/synthetic_accidents_training_features.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:19487c308417be0a125f6f5e57683ddd34bac7056776d34cfbe86f450246dca7
|
| 3 |
+
size 1280490
|
data/schema.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data Schema Definitions
|
| 3 |
+
=======================
|
| 4 |
+
Defines the structure of data used throughout the Traffic Accident
|
| 5 |
+
Reconstruction System.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from dataclasses import dataclass, field
|
| 9 |
+
from typing import List, Tuple, Optional, Dict, Any
|
| 10 |
+
from enum import Enum
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# ============================================================
|
| 15 |
+
# ENUMERATIONS
|
| 16 |
+
# ============================================================
|
| 17 |
+
|
| 18 |
+
class VehicleType(Enum):
|
| 19 |
+
"""Types of vehicles."""
|
| 20 |
+
SEDAN = "sedan"
|
| 21 |
+
SUV = "suv"
|
| 22 |
+
TRUCK = "truck"
|
| 23 |
+
MOTORCYCLE = "motorcycle"
|
| 24 |
+
BUS = "bus"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Direction(Enum):
|
| 28 |
+
"""Cardinal and intercardinal directions."""
|
| 29 |
+
NORTH = "north"
|
| 30 |
+
NORTHEAST = "northeast"
|
| 31 |
+
EAST = "east"
|
| 32 |
+
SOUTHEAST = "southeast"
|
| 33 |
+
SOUTH = "south"
|
| 34 |
+
SOUTHWEST = "southwest"
|
| 35 |
+
WEST = "west"
|
| 36 |
+
NORTHWEST = "northwest"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class VehicleAction(Enum):
|
| 40 |
+
"""Possible vehicle actions."""
|
| 41 |
+
GOING_STRAIGHT = "going_straight"
|
| 42 |
+
TURNING_LEFT = "turning_left"
|
| 43 |
+
TURNING_RIGHT = "turning_right"
|
| 44 |
+
ENTERING_ROUNDABOUT = "entering_roundabout"
|
| 45 |
+
EXITING_ROUNDABOUT = "exiting_roundabout"
|
| 46 |
+
CHANGING_LANE_LEFT = "changing_lane_left"
|
| 47 |
+
CHANGING_LANE_RIGHT = "changing_lane_right"
|
| 48 |
+
SLOWING_DOWN = "slowing_down"
|
| 49 |
+
ACCELERATING = "accelerating"
|
| 50 |
+
STOPPED = "stopped"
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class WeatherCondition(Enum):
|
| 54 |
+
"""Weather conditions."""
|
| 55 |
+
CLEAR = "clear"
|
| 56 |
+
CLOUDY = "cloudy"
|
| 57 |
+
RAINY = "rainy"
|
| 58 |
+
FOGGY = "foggy"
|
| 59 |
+
SANDSTORM = "sandstorm"
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class RoadCondition(Enum):
|
| 63 |
+
"""Road surface conditions."""
|
| 64 |
+
DRY = "dry"
|
| 65 |
+
WET = "wet"
|
| 66 |
+
SANDY = "sandy"
|
| 67 |
+
OILY = "oily"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class AccidentType(Enum):
|
| 71 |
+
"""Types of traffic accidents."""
|
| 72 |
+
REAR_END_COLLISION = "rear_end_collision"
|
| 73 |
+
SIDE_IMPACT = "side_impact"
|
| 74 |
+
HEAD_ON_COLLISION = "head_on_collision"
|
| 75 |
+
SIDESWIPE = "sideswipe"
|
| 76 |
+
ROUNDABOUT_ENTRY_COLLISION = "roundabout_entry_collision"
|
| 77 |
+
LANE_CHANGE_COLLISION = "lane_change_collision"
|
| 78 |
+
INTERSECTION_COLLISION = "intersection_collision"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class Severity(Enum):
|
| 82 |
+
"""Accident severity levels."""
|
| 83 |
+
MINOR = "minor"
|
| 84 |
+
MODERATE = "moderate"
|
| 85 |
+
SEVERE = "severe"
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class RoadType(Enum):
|
| 89 |
+
"""Types of roads."""
|
| 90 |
+
ROUNDABOUT = "roundabout"
|
| 91 |
+
INTERSECTION = "intersection"
|
| 92 |
+
HIGHWAY = "highway"
|
| 93 |
+
URBAN_ROAD = "urban_road"
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# ============================================================
|
| 97 |
+
# DATA CLASSES
|
| 98 |
+
# ============================================================
|
| 99 |
+
|
| 100 |
+
@dataclass
|
| 101 |
+
class Location:
|
| 102 |
+
"""Geographic location data."""
|
| 103 |
+
name: str
|
| 104 |
+
name_arabic: str = ""
|
| 105 |
+
latitude: float = 0.0
|
| 106 |
+
longitude: float = 0.0
|
| 107 |
+
radius_meters: int = 150
|
| 108 |
+
city: str = ""
|
| 109 |
+
country: str = ""
|
| 110 |
+
road_type: RoadType = RoadType.ROUNDABOUT
|
| 111 |
+
|
| 112 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 113 |
+
return {
|
| 114 |
+
"name": self.name,
|
| 115 |
+
"name_arabic": self.name_arabic,
|
| 116 |
+
"latitude": self.latitude,
|
| 117 |
+
"longitude": self.longitude,
|
| 118 |
+
"radius_meters": self.radius_meters,
|
| 119 |
+
"city": self.city,
|
| 120 |
+
"country": self.country,
|
| 121 |
+
"road_type": self.road_type.value
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@dataclass
|
| 126 |
+
class Conditions:
|
| 127 |
+
"""Environmental conditions at time of accident."""
|
| 128 |
+
weather: WeatherCondition = WeatherCondition.CLEAR
|
| 129 |
+
road_condition: RoadCondition = RoadCondition.DRY
|
| 130 |
+
visibility: float = 1.0 # 0.0 to 1.0
|
| 131 |
+
lighting: str = "daylight" # daylight, dusk, night, artificial
|
| 132 |
+
|
| 133 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 134 |
+
return {
|
| 135 |
+
"weather": self.weather.value,
|
| 136 |
+
"road_condition": self.road_condition.value,
|
| 137 |
+
"visibility": self.visibility,
|
| 138 |
+
"lighting": self.lighting
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
@dataclass
|
| 143 |
+
class VehicleData:
|
| 144 |
+
"""Data for a single vehicle."""
|
| 145 |
+
vehicle_type: VehicleType = VehicleType.SEDAN
|
| 146 |
+
speed_kmh: float = 50.0
|
| 147 |
+
direction: Direction = Direction.NORTH
|
| 148 |
+
action: VehicleAction = VehicleAction.GOING_STRAIGHT
|
| 149 |
+
braking: bool = False
|
| 150 |
+
signaling: bool = False
|
| 151 |
+
lights_on: bool = True
|
| 152 |
+
horn_used: bool = False
|
| 153 |
+
path: List[Tuple[float, float]] = field(default_factory=list)
|
| 154 |
+
description: str = ""
|
| 155 |
+
|
| 156 |
+
@property
|
| 157 |
+
def direction_angle(self) -> float:
|
| 158 |
+
"""Convert direction to angle in degrees."""
|
| 159 |
+
angles = {
|
| 160 |
+
Direction.NORTH: 0,
|
| 161 |
+
Direction.NORTHEAST: 45,
|
| 162 |
+
Direction.EAST: 90,
|
| 163 |
+
Direction.SOUTHEAST: 135,
|
| 164 |
+
Direction.SOUTH: 180,
|
| 165 |
+
Direction.SOUTHWEST: 225,
|
| 166 |
+
Direction.WEST: 270,
|
| 167 |
+
Direction.NORTHWEST: 315
|
| 168 |
+
}
|
| 169 |
+
return angles.get(self.direction, 0)
|
| 170 |
+
|
| 171 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 172 |
+
return {
|
| 173 |
+
"type": self.vehicle_type.value,
|
| 174 |
+
"speed_kmh": self.speed_kmh,
|
| 175 |
+
"direction": self.direction.value,
|
| 176 |
+
"direction_angle": self.direction_angle,
|
| 177 |
+
"action": self.action.value,
|
| 178 |
+
"braking": self.braking,
|
| 179 |
+
"signaling": self.signaling,
|
| 180 |
+
"lights_on": self.lights_on,
|
| 181 |
+
"horn_used": self.horn_used,
|
| 182 |
+
"path": self.path,
|
| 183 |
+
"description": self.description
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@dataclass
|
| 188 |
+
class AccidentDetails:
|
| 189 |
+
"""Details about the accident."""
|
| 190 |
+
accident_type: AccidentType = AccidentType.SIDE_IMPACT
|
| 191 |
+
severity: Severity = Severity.MODERATE
|
| 192 |
+
collision_angle: float = 90.0
|
| 193 |
+
collision_point: Tuple[float, float] = (0.0, 0.0)
|
| 194 |
+
contributing_factors: List[str] = field(default_factory=list)
|
| 195 |
+
fault_vehicle: int = 0 # 0 = undetermined, 1 or 2
|
| 196 |
+
|
| 197 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 198 |
+
return {
|
| 199 |
+
"type": self.accident_type.value,
|
| 200 |
+
"severity": self.severity.value,
|
| 201 |
+
"collision_angle": self.collision_angle,
|
| 202 |
+
"collision_point": list(self.collision_point),
|
| 203 |
+
"contributing_factors": self.contributing_factors,
|
| 204 |
+
"fault_vehicle": self.fault_vehicle
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
@dataclass
|
| 209 |
+
class ScenarioMetrics:
|
| 210 |
+
"""Metrics for an accident scenario."""
|
| 211 |
+
collision_probability: float = 0.5
|
| 212 |
+
path_overlap: float = 0.5
|
| 213 |
+
speed_differential: float = 0.0
|
| 214 |
+
time_to_collision: float = 0.0
|
| 215 |
+
impact_force_estimate: float = 0.0
|
| 216 |
+
|
| 217 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 218 |
+
return {
|
| 219 |
+
"collision_probability": self.collision_probability,
|
| 220 |
+
"path_overlap": self.path_overlap,
|
| 221 |
+
"speed_differential": self.speed_differential,
|
| 222 |
+
"time_to_collision": self.time_to_collision,
|
| 223 |
+
"impact_force_estimate": self.impact_force_estimate
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dataclass
|
| 228 |
+
class Scenario:
|
| 229 |
+
"""A single accident scenario."""
|
| 230 |
+
scenario_id: int = 0
|
| 231 |
+
accident_type: AccidentType = AccidentType.SIDE_IMPACT
|
| 232 |
+
probability: float = 0.5
|
| 233 |
+
description: str = ""
|
| 234 |
+
contributing_factors: List[str] = field(default_factory=list)
|
| 235 |
+
metrics: ScenarioMetrics = field(default_factory=ScenarioMetrics)
|
| 236 |
+
vehicle_1_path: List[Tuple[float, float]] = field(default_factory=list)
|
| 237 |
+
vehicle_2_path: List[Tuple[float, float]] = field(default_factory=list)
|
| 238 |
+
collision_point: Tuple[float, float] = (0.0, 0.0)
|
| 239 |
+
|
| 240 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 241 |
+
return {
|
| 242 |
+
"id": self.scenario_id,
|
| 243 |
+
"accident_type": self.accident_type.value,
|
| 244 |
+
"probability": self.probability,
|
| 245 |
+
"description": self.description,
|
| 246 |
+
"contributing_factors": self.contributing_factors,
|
| 247 |
+
"metrics": self.metrics.to_dict(),
|
| 248 |
+
"vehicle_1_path": self.vehicle_1_path,
|
| 249 |
+
"vehicle_2_path": self.vehicle_2_path,
|
| 250 |
+
"collision_point": list(self.collision_point)
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
@dataclass
|
| 255 |
+
class FaultAssessment:
|
| 256 |
+
"""Preliminary fault assessment."""
|
| 257 |
+
vehicle_1_contribution: float = 50.0 # Percentage
|
| 258 |
+
vehicle_2_contribution: float = 50.0
|
| 259 |
+
likely_at_fault: int = 0 # 0 = undetermined, 1 or 2
|
| 260 |
+
primary_factor: str = ""
|
| 261 |
+
confidence: float = 0.5
|
| 262 |
+
|
| 263 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 264 |
+
return {
|
| 265 |
+
"vehicle_1_contribution": self.vehicle_1_contribution,
|
| 266 |
+
"vehicle_2_contribution": self.vehicle_2_contribution,
|
| 267 |
+
"likely_at_fault": self.likely_at_fault,
|
| 268 |
+
"primary_factor": self.primary_factor,
|
| 269 |
+
"confidence": self.confidence
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
@dataclass
|
| 274 |
+
class TimelineEvent:
|
| 275 |
+
"""An event in the accident timeline."""
|
| 276 |
+
time_offset: float = 0.0 # Seconds relative to collision (negative = before)
|
| 277 |
+
event: str = ""
|
| 278 |
+
|
| 279 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 280 |
+
return {
|
| 281 |
+
"time": self.time_offset,
|
| 282 |
+
"event": self.event
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
@dataclass
|
| 287 |
+
class AccidentRecord:
|
| 288 |
+
"""Complete accident record."""
|
| 289 |
+
accident_id: str = ""
|
| 290 |
+
timestamp: datetime = field(default_factory=datetime.now)
|
| 291 |
+
location: Location = field(default_factory=Location)
|
| 292 |
+
conditions: Conditions = field(default_factory=Conditions)
|
| 293 |
+
vehicle_1: VehicleData = field(default_factory=VehicleData)
|
| 294 |
+
vehicle_2: VehicleData = field(default_factory=VehicleData)
|
| 295 |
+
accident_details: AccidentDetails = field(default_factory=AccidentDetails)
|
| 296 |
+
|
| 297 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 298 |
+
return {
|
| 299 |
+
"accident_id": self.accident_id,
|
| 300 |
+
"timestamp": self.timestamp.isoformat(),
|
| 301 |
+
"location": self.location.to_dict(),
|
| 302 |
+
"conditions": self.conditions.to_dict(),
|
| 303 |
+
"vehicle_1": self.vehicle_1.to_dict(),
|
| 304 |
+
"vehicle_2": self.vehicle_2.to_dict(),
|
| 305 |
+
"accident_details": self.accident_details.to_dict()
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
@dataclass
|
| 310 |
+
class AnalysisResult:
|
| 311 |
+
"""Results from AI analysis."""
|
| 312 |
+
scenarios: List[Scenario] = field(default_factory=list)
|
| 313 |
+
most_likely_scenario_id: int = 0
|
| 314 |
+
overall_collision_probability: float = 0.5
|
| 315 |
+
fault_assessment: FaultAssessment = field(default_factory=FaultAssessment)
|
| 316 |
+
timeline: List[TimelineEvent] = field(default_factory=list)
|
| 317 |
+
analysis_timestamp: datetime = field(default_factory=datetime.now)
|
| 318 |
+
|
| 319 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 320 |
+
return {
|
| 321 |
+
"scenarios": [s.to_dict() for s in self.scenarios],
|
| 322 |
+
"most_likely_scenario": {
|
| 323 |
+
"id": self.most_likely_scenario_id,
|
| 324 |
+
"probability": self.scenarios[self.most_likely_scenario_id - 1].probability
|
| 325 |
+
if self.scenarios else 0
|
| 326 |
+
},
|
| 327 |
+
"overall_collision_probability": self.overall_collision_probability,
|
| 328 |
+
"preliminary_fault_assessment": self.fault_assessment.to_dict(),
|
| 329 |
+
"timeline": [e.to_dict() for e in self.timeline],
|
| 330 |
+
"analysis_timestamp": self.analysis_timestamp.isoformat()
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
# ============================================================
|
| 335 |
+
# FEATURE VECTOR SCHEMA (for MindSpore)
|
| 336 |
+
# ============================================================
|
| 337 |
+
|
| 338 |
+
FEATURE_SCHEMA = {
|
| 339 |
+
"input_features": [
|
| 340 |
+
# Vehicle 1 features (7)
|
| 341 |
+
"v1_type_encoded", # 0-4 normalized
|
| 342 |
+
"v1_speed_normalized", # 0-1 (speed/200)
|
| 343 |
+
"v1_direction_encoded", # 0-1 (direction/8)
|
| 344 |
+
"v1_angle_normalized", # 0-1 (angle/360)
|
| 345 |
+
"v1_action_encoded", # 0-1 (action/10)
|
| 346 |
+
"v1_braking", # 0 or 1
|
| 347 |
+
"v1_signaling", # 0 or 1
|
| 348 |
+
|
| 349 |
+
# Vehicle 2 features (7)
|
| 350 |
+
"v2_type_encoded",
|
| 351 |
+
"v2_speed_normalized",
|
| 352 |
+
"v2_direction_encoded",
|
| 353 |
+
"v2_angle_normalized",
|
| 354 |
+
"v2_action_encoded",
|
| 355 |
+
"v2_braking",
|
| 356 |
+
"v2_signaling",
|
| 357 |
+
|
| 358 |
+
# Environmental features (3)
|
| 359 |
+
"weather_encoded", # 0-1 (weather/5)
|
| 360 |
+
"road_condition_encoded",# 0-1 (condition/4)
|
| 361 |
+
"visibility", # 0-1
|
| 362 |
+
|
| 363 |
+
# Derived features (6)
|
| 364 |
+
"collision_angle_normalized", # 0-1 (angle/180)
|
| 365 |
+
"speed_differential", # 0-1 (diff/200)
|
| 366 |
+
"combined_speed_normalized", # 0-1 (sum/400)
|
| 367 |
+
"same_direction", # 0 or 1
|
| 368 |
+
"speed_product_normalized", # 0-1
|
| 369 |
+
"angle_difference_normalized" # -1 to 1
|
| 370 |
+
],
|
| 371 |
+
"total_input_features": 23,
|
| 372 |
+
|
| 373 |
+
"output_labels": [
|
| 374 |
+
"rear_end_collision",
|
| 375 |
+
"side_impact",
|
| 376 |
+
"head_on_collision",
|
| 377 |
+
"sideswipe",
|
| 378 |
+
"roundabout_entry_collision",
|
| 379 |
+
"lane_change_collision",
|
| 380 |
+
"intersection_collision"
|
| 381 |
+
],
|
| 382 |
+
"total_output_classes": 7
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
# ============================================================
|
| 387 |
+
# VALIDATION FUNCTIONS
|
| 388 |
+
# ============================================================
|
| 389 |
+
|
| 390 |
+
def validate_speed(speed: float, vehicle_type: VehicleType) -> bool:
|
| 391 |
+
"""Validate speed is within acceptable range for vehicle type."""
|
| 392 |
+
max_speeds = {
|
| 393 |
+
VehicleType.SEDAN: 180,
|
| 394 |
+
VehicleType.SUV: 160,
|
| 395 |
+
VehicleType.TRUCK: 120,
|
| 396 |
+
VehicleType.MOTORCYCLE: 200,
|
| 397 |
+
VehicleType.BUS: 100
|
| 398 |
+
}
|
| 399 |
+
return 0 <= speed <= max_speeds.get(vehicle_type, 200)
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def validate_coordinates(lat: float, lng: float) -> bool:
|
| 403 |
+
"""Validate geographic coordinates."""
|
| 404 |
+
return -90 <= lat <= 90 and -180 <= lng <= 180
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def validate_path(path: List[Tuple[float, float]]) -> bool:
|
| 408 |
+
"""Validate vehicle path has at least 2 points."""
|
| 409 |
+
if len(path) < 2:
|
| 410 |
+
return False
|
| 411 |
+
return all(validate_coordinates(p[0], p[1]) for p in path)
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def validate_probability(prob: float) -> bool:
|
| 415 |
+
"""Validate probability is between 0 and 1."""
|
| 416 |
+
return 0 <= prob <= 1
|
data/synthetic_dataset_generator.py
ADDED
|
@@ -0,0 +1,967 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Synthetic Accident Dataset Generator
|
| 3 |
+
=====================================
|
| 4 |
+
Generates realistic synthetic traffic accident data for training
|
| 5 |
+
the MindSpore AI model.
|
| 6 |
+
|
| 7 |
+
This dataset simulates various accident scenarios at roundabouts
|
| 8 |
+
with different vehicle types, speeds, directions, and conditions.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import pandas as pd
|
| 13 |
+
import json
|
| 14 |
+
import random
|
| 15 |
+
from datetime import datetime, timedelta
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
from typing import Dict, List, Tuple, Any
|
| 18 |
+
|
| 19 |
+
import sys
|
| 20 |
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 21 |
+
|
| 22 |
+
from config import (
|
| 23 |
+
CASE_STUDY_LOCATION,
|
| 24 |
+
VEHICLE_TYPES,
|
| 25 |
+
ACCIDENT_TYPES,
|
| 26 |
+
CONTRIBUTING_FACTORS,
|
| 27 |
+
ROAD_TYPES,
|
| 28 |
+
DATA_DIR,
|
| 29 |
+
PROCESSED_DATA_DIR
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ============================================================
|
| 34 |
+
# CONSTANTS FOR DATA GENERATION
|
| 35 |
+
# ============================================================
|
| 36 |
+
|
| 37 |
+
# Directions with angles (for roundabout entry/exit)
|
| 38 |
+
DIRECTIONS = {
|
| 39 |
+
'north': 0,
|
| 40 |
+
'northeast': 45,
|
| 41 |
+
'east': 90,
|
| 42 |
+
'southeast': 135,
|
| 43 |
+
'south': 180,
|
| 44 |
+
'southwest': 225,
|
| 45 |
+
'west': 270,
|
| 46 |
+
'northwest': 315
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
# Actions vehicles can take
|
| 50 |
+
VEHICLE_ACTIONS = [
|
| 51 |
+
'going_straight',
|
| 52 |
+
'turning_left',
|
| 53 |
+
'turning_right',
|
| 54 |
+
'entering_roundabout',
|
| 55 |
+
'exiting_roundabout',
|
| 56 |
+
'changing_lane_left',
|
| 57 |
+
'changing_lane_right',
|
| 58 |
+
'slowing_down',
|
| 59 |
+
'accelerating',
|
| 60 |
+
'stopped'
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
# Weather conditions with probability weights
|
| 64 |
+
WEATHER_CONDITIONS = {
|
| 65 |
+
'clear': 0.55,
|
| 66 |
+
'cloudy': 0.20,
|
| 67 |
+
'rainy': 0.12,
|
| 68 |
+
'foggy': 0.07,
|
| 69 |
+
'sandstorm': 0.06
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
# Road conditions with probability weights
|
| 73 |
+
ROAD_CONDITIONS = {
|
| 74 |
+
'dry': 0.65,
|
| 75 |
+
'wet': 0.18,
|
| 76 |
+
'sandy': 0.12,
|
| 77 |
+
'oily': 0.05
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
# Road types with probability weights (expanded)
|
| 81 |
+
ROAD_TYPE_WEIGHTS = {
|
| 82 |
+
'roundabout': 0.30,
|
| 83 |
+
'crossroad': 0.25,
|
| 84 |
+
't_junction': 0.15,
|
| 85 |
+
'highway_merge': 0.10,
|
| 86 |
+
'parking': 0.05,
|
| 87 |
+
'highway': 0.08,
|
| 88 |
+
'urban_road': 0.05,
|
| 89 |
+
'other': 0.02
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
# Time of day distribution (hour: probability)
|
| 93 |
+
TIME_DISTRIBUTION = {
|
| 94 |
+
'morning_rush': (7, 9, 0.25), # 7-9 AM, 25% of accidents
|
| 95 |
+
'midday': (10, 15, 0.20), # 10 AM - 3 PM, 20%
|
| 96 |
+
'evening_rush': (16, 19, 0.30), # 4-7 PM, 30%
|
| 97 |
+
'night': (20, 23, 0.15), # 8-11 PM, 15%
|
| 98 |
+
'late_night': (0, 6, 0.10) # Midnight - 6 AM, 10%
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
# Lighting conditions
|
| 102 |
+
LIGHTING_CONDITIONS = ['daylight', 'dusk', 'dawn', 'night_lit', 'night_dark']
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# ============================================================
|
| 106 |
+
# DATA SCHEMA DEFINITION
|
| 107 |
+
# ============================================================
|
| 108 |
+
|
| 109 |
+
ACCIDENT_SCHEMA = {
|
| 110 |
+
"accident_id": "string",
|
| 111 |
+
"timestamp": "datetime",
|
| 112 |
+
"location": {
|
| 113 |
+
"name": "string",
|
| 114 |
+
"latitude": "float",
|
| 115 |
+
"longitude": "float",
|
| 116 |
+
"road_type": "string"
|
| 117 |
+
},
|
| 118 |
+
"conditions": {
|
| 119 |
+
"weather": "string",
|
| 120 |
+
"road_condition": "string",
|
| 121 |
+
"visibility": "float", # 0-1 scale
|
| 122 |
+
"lighting": "string" # daylight, dusk, night, artificial
|
| 123 |
+
},
|
| 124 |
+
"vehicle_1": {
|
| 125 |
+
"type": "string",
|
| 126 |
+
"speed_kmh": "float",
|
| 127 |
+
"direction": "string",
|
| 128 |
+
"direction_angle": "float",
|
| 129 |
+
"action": "string",
|
| 130 |
+
"braking": "boolean",
|
| 131 |
+
"signaling": "boolean",
|
| 132 |
+
"path": "list[tuple]"
|
| 133 |
+
},
|
| 134 |
+
"vehicle_2": {
|
| 135 |
+
"type": "string",
|
| 136 |
+
"speed_kmh": "float",
|
| 137 |
+
"direction": "string",
|
| 138 |
+
"direction_angle": "float",
|
| 139 |
+
"action": "string",
|
| 140 |
+
"braking": "boolean",
|
| 141 |
+
"signaling": "boolean",
|
| 142 |
+
"path": "list[tuple]"
|
| 143 |
+
},
|
| 144 |
+
"accident_details": {
|
| 145 |
+
"type": "string",
|
| 146 |
+
"severity": "string", # minor, moderate, severe
|
| 147 |
+
"collision_angle": "float",
|
| 148 |
+
"collision_point": "tuple",
|
| 149 |
+
"contributing_factors": "list[string]",
|
| 150 |
+
"fault_vehicle": "int" # 1 or 2
|
| 151 |
+
},
|
| 152 |
+
"outcomes": {
|
| 153 |
+
"scenario_probability": "float",
|
| 154 |
+
"damage_estimate": "string",
|
| 155 |
+
"injuries": "boolean"
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# ============================================================
|
| 161 |
+
# HELPER FUNCTIONS
|
| 162 |
+
# ============================================================
|
| 163 |
+
|
| 164 |
+
def generate_accident_id() -> str:
|
| 165 |
+
"""Generate unique accident ID."""
|
| 166 |
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
| 167 |
+
random_suffix = ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=4))
|
| 168 |
+
return f"ACC-{timestamp}-{random_suffix}"
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def generate_timestamp() -> datetime:
|
| 172 |
+
"""Generate realistic accident timestamp based on distribution."""
|
| 173 |
+
# Select time period based on weights
|
| 174 |
+
period = random.choices(
|
| 175 |
+
list(TIME_DISTRIBUTION.keys()),
|
| 176 |
+
weights=[v[2] for v in TIME_DISTRIBUTION.values()]
|
| 177 |
+
)[0]
|
| 178 |
+
|
| 179 |
+
start_hour, end_hour, _ = TIME_DISTRIBUTION[period]
|
| 180 |
+
|
| 181 |
+
# Generate random date within last year
|
| 182 |
+
days_ago = random.randint(0, 365)
|
| 183 |
+
base_date = datetime.now() - timedelta(days=days_ago)
|
| 184 |
+
|
| 185 |
+
# Generate random time within period
|
| 186 |
+
hour = random.randint(start_hour, end_hour)
|
| 187 |
+
minute = random.randint(0, 59)
|
| 188 |
+
|
| 189 |
+
return base_date.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def select_weather() -> Tuple[str, float]:
|
| 193 |
+
"""Select weather condition and corresponding visibility."""
|
| 194 |
+
weather = random.choices(
|
| 195 |
+
list(WEATHER_CONDITIONS.keys()),
|
| 196 |
+
weights=list(WEATHER_CONDITIONS.values())
|
| 197 |
+
)[0]
|
| 198 |
+
|
| 199 |
+
visibility_map = {
|
| 200 |
+
'clear': random.uniform(0.9, 1.0),
|
| 201 |
+
'cloudy': random.uniform(0.8, 0.95),
|
| 202 |
+
'rainy': random.uniform(0.5, 0.8),
|
| 203 |
+
'foggy': random.uniform(0.2, 0.5),
|
| 204 |
+
'sandstorm': random.uniform(0.1, 0.4)
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
return weather, visibility_map[weather]
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def select_road_condition(weather: str) -> str:
|
| 211 |
+
"""Select road condition based on weather."""
|
| 212 |
+
if weather == 'rainy':
|
| 213 |
+
return 'wet'
|
| 214 |
+
elif weather == 'sandstorm':
|
| 215 |
+
return random.choice(['sandy', 'dry'])
|
| 216 |
+
else:
|
| 217 |
+
return random.choices(
|
| 218 |
+
list(ROAD_CONDITIONS.keys()),
|
| 219 |
+
weights=list(ROAD_CONDITIONS.values())
|
| 220 |
+
)[0]
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def generate_vehicle_data(vehicle_num: int, accident_type: str, road_type: str = 'roundabout') -> Dict:
|
| 224 |
+
"""Generate realistic vehicle data based on accident type and road type."""
|
| 225 |
+
|
| 226 |
+
# Select vehicle type with realistic distribution
|
| 227 |
+
vehicle_type = random.choices(
|
| 228 |
+
list(VEHICLE_TYPES.keys()),
|
| 229 |
+
weights=[0.50, 0.30, 0.10, 0.05, 0.05] # sedan most common
|
| 230 |
+
)[0]
|
| 231 |
+
|
| 232 |
+
specs = VEHICLE_TYPES[vehicle_type]
|
| 233 |
+
|
| 234 |
+
# Generate speed based on accident type and road type
|
| 235 |
+
speed_modifier = {
|
| 236 |
+
'roundabout': 0.6,
|
| 237 |
+
'crossroad': 0.7,
|
| 238 |
+
't_junction': 0.65,
|
| 239 |
+
'highway_merge': 0.9,
|
| 240 |
+
'parking': 0.2,
|
| 241 |
+
'highway': 1.0,
|
| 242 |
+
'urban_road': 0.5,
|
| 243 |
+
'other': 0.6
|
| 244 |
+
}.get(road_type, 0.6)
|
| 245 |
+
|
| 246 |
+
if accident_type == 'rear_end_collision':
|
| 247 |
+
if vehicle_num == 1:
|
| 248 |
+
speed = random.uniform(20, 50) * speed_modifier
|
| 249 |
+
else:
|
| 250 |
+
speed = random.uniform(40, 80) * speed_modifier
|
| 251 |
+
elif accident_type == 'head_on_collision':
|
| 252 |
+
speed = random.uniform(50, 100) * speed_modifier
|
| 253 |
+
elif accident_type in ['roundabout_entry_collision', 'intersection_collision']:
|
| 254 |
+
speed = random.uniform(30, 60) * speed_modifier
|
| 255 |
+
else:
|
| 256 |
+
speed = random.uniform(30, specs['max_speed'] * 0.7) * speed_modifier
|
| 257 |
+
|
| 258 |
+
# Ensure speed doesn't exceed vehicle max
|
| 259 |
+
speed = min(speed, specs['max_speed'])
|
| 260 |
+
|
| 261 |
+
# Select direction
|
| 262 |
+
direction = random.choice(list(DIRECTIONS.keys()))
|
| 263 |
+
|
| 264 |
+
# Select action based on accident type and road type
|
| 265 |
+
if road_type == 'roundabout':
|
| 266 |
+
if accident_type == 'roundabout_entry_collision':
|
| 267 |
+
action = random.choice(['entering_roundabout', 'going_straight'])
|
| 268 |
+
else:
|
| 269 |
+
action = random.choice(['entering_roundabout', 'exiting_roundabout', 'going_straight'])
|
| 270 |
+
elif road_type in ['crossroad', 't_junction']:
|
| 271 |
+
action = random.choice(['going_straight', 'turning_left', 'turning_right', 'stopped'])
|
| 272 |
+
elif road_type == 'highway_merge':
|
| 273 |
+
action = random.choice(['going_straight', 'changing_lane_left', 'changing_lane_right', 'accelerating'])
|
| 274 |
+
elif road_type == 'parking':
|
| 275 |
+
action = random.choice(['slowing_down', 'stopped', 'going_straight'])
|
| 276 |
+
elif road_type == 'highway':
|
| 277 |
+
action = random.choice(['going_straight', 'changing_lane_left', 'changing_lane_right'])
|
| 278 |
+
else:
|
| 279 |
+
if accident_type == 'lane_change_collision':
|
| 280 |
+
action = random.choice(['changing_lane_left', 'changing_lane_right'])
|
| 281 |
+
elif accident_type == 'rear_end_collision':
|
| 282 |
+
action = 'going_straight' if vehicle_num == 2 else random.choice(['slowing_down', 'stopped'])
|
| 283 |
+
else:
|
| 284 |
+
action = random.choice(VEHICLE_ACTIONS)
|
| 285 |
+
|
| 286 |
+
# Braking and signaling
|
| 287 |
+
braking = random.random() < 0.4 # 40% chance of braking
|
| 288 |
+
signaling = random.random() < 0.3 # 30% chance of signaling
|
| 289 |
+
|
| 290 |
+
# Generate simplified path (entry point, intermediate, collision area)
|
| 291 |
+
path = generate_vehicle_path(direction, accident_type)
|
| 292 |
+
|
| 293 |
+
return {
|
| 294 |
+
'type': vehicle_type,
|
| 295 |
+
'speed_kmh': round(speed, 1),
|
| 296 |
+
'direction': direction,
|
| 297 |
+
'direction_angle': DIRECTIONS[direction],
|
| 298 |
+
'action': action,
|
| 299 |
+
'braking': braking,
|
| 300 |
+
'signaling': signaling,
|
| 301 |
+
'path': path
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def generate_vehicle_path(direction: str, accident_type: str) -> List[List[float]]:
|
| 306 |
+
"""Generate a realistic vehicle path for the roundabout."""
|
| 307 |
+
|
| 308 |
+
base_lat = CASE_STUDY_LOCATION['latitude']
|
| 309 |
+
base_lng = CASE_STUDY_LOCATION['longitude']
|
| 310 |
+
|
| 311 |
+
# Offset based on direction (entry points)
|
| 312 |
+
direction_offsets = {
|
| 313 |
+
'north': (0.002, 0),
|
| 314 |
+
'south': (-0.002, 0),
|
| 315 |
+
'east': (0, 0.002),
|
| 316 |
+
'west': (0, -0.002),
|
| 317 |
+
'northeast': (0.0015, 0.0015),
|
| 318 |
+
'northwest': (0.0015, -0.0015),
|
| 319 |
+
'southeast': (-0.0015, 0.0015),
|
| 320 |
+
'southwest': (-0.0015, -0.0015)
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
offset = direction_offsets.get(direction, (0.002, 0))
|
| 324 |
+
|
| 325 |
+
# Generate path points
|
| 326 |
+
start_lat = base_lat + offset[0]
|
| 327 |
+
start_lng = base_lng + offset[1]
|
| 328 |
+
|
| 329 |
+
# Path moves toward center (collision zone)
|
| 330 |
+
path = [
|
| 331 |
+
[start_lat, start_lng],
|
| 332 |
+
[start_lat - offset[0] * 0.5, start_lng - offset[1] * 0.5],
|
| 333 |
+
[base_lat + random.uniform(-0.0003, 0.0003),
|
| 334 |
+
base_lng + random.uniform(-0.0003, 0.0003)]
|
| 335 |
+
]
|
| 336 |
+
|
| 337 |
+
return path
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def calculate_collision_angle(v1_direction: str, v2_direction: str) -> float:
|
| 341 |
+
"""Calculate the angle of collision between two vehicles."""
|
| 342 |
+
angle1 = DIRECTIONS[v1_direction]
|
| 343 |
+
angle2 = DIRECTIONS[v2_direction]
|
| 344 |
+
|
| 345 |
+
diff = abs(angle1 - angle2)
|
| 346 |
+
if diff > 180:
|
| 347 |
+
diff = 360 - diff
|
| 348 |
+
|
| 349 |
+
return diff
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def determine_accident_type(v1_direction: str, v2_direction: str,
|
| 353 |
+
v1_action: str, v2_action: str,
|
| 354 |
+
road_type: str = 'roundabout') -> str:
|
| 355 |
+
"""Determine accident type based on vehicle directions, actions, and road type."""
|
| 356 |
+
|
| 357 |
+
collision_angle = calculate_collision_angle(v1_direction, v2_direction)
|
| 358 |
+
|
| 359 |
+
# Head-on: ~180 degrees
|
| 360 |
+
if collision_angle > 150:
|
| 361 |
+
return 'head_on_collision'
|
| 362 |
+
|
| 363 |
+
# Rear-end: ~0 degrees, same direction
|
| 364 |
+
if collision_angle < 30:
|
| 365 |
+
return 'rear_end_collision'
|
| 366 |
+
|
| 367 |
+
# Side impact: ~90 degrees
|
| 368 |
+
if 60 < collision_angle < 120:
|
| 369 |
+
return 'side_impact'
|
| 370 |
+
|
| 371 |
+
# Roundabout specific
|
| 372 |
+
if road_type == 'roundabout' and ('roundabout' in v1_action or 'roundabout' in v2_action):
|
| 373 |
+
return 'roundabout_entry_collision'
|
| 374 |
+
|
| 375 |
+
# Lane change
|
| 376 |
+
if 'changing_lane' in v1_action or 'changing_lane' in v2_action:
|
| 377 |
+
return 'lane_change_collision'
|
| 378 |
+
|
| 379 |
+
# Intersection/crossroad collision
|
| 380 |
+
if road_type in ['crossroad', 't_junction']:
|
| 381 |
+
return 'intersection_collision'
|
| 382 |
+
|
| 383 |
+
# Default sideswipe for smaller angles
|
| 384 |
+
if 30 <= collision_angle <= 60:
|
| 385 |
+
return 'sideswipe'
|
| 386 |
+
|
| 387 |
+
# Default to intersection collision
|
| 388 |
+
return 'intersection_collision'
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def determine_contributing_factors(
|
| 392 |
+
v1_data: Dict,
|
| 393 |
+
v2_data: Dict,
|
| 394 |
+
weather: str,
|
| 395 |
+
road_condition: str,
|
| 396 |
+
road_type: str = 'roundabout'
|
| 397 |
+
) -> List[str]:
|
| 398 |
+
"""Determine contributing factors based on accident data."""
|
| 399 |
+
|
| 400 |
+
factors = []
|
| 401 |
+
|
| 402 |
+
# Speed-related
|
| 403 |
+
speed_limits = {
|
| 404 |
+
'roundabout': 50, 'crossroad': 60, 't_junction': 50,
|
| 405 |
+
'highway_merge': 80, 'parking': 20, 'highway': 120, 'urban_road': 50, 'other': 60
|
| 406 |
+
}
|
| 407 |
+
speed_limit = speed_limits.get(road_type, 60)
|
| 408 |
+
|
| 409 |
+
if v1_data['speed_kmh'] > speed_limit or v2_data['speed_kmh'] > speed_limit:
|
| 410 |
+
factors.append('speeding')
|
| 411 |
+
|
| 412 |
+
# Following distance (for similar directions)
|
| 413 |
+
collision_angle = calculate_collision_angle(v1_data['direction'], v2_data['direction'])
|
| 414 |
+
if collision_angle < 30 and abs(v1_data['speed_kmh'] - v2_data['speed_kmh']) > 20:
|
| 415 |
+
factors.append('following_too_closely')
|
| 416 |
+
|
| 417 |
+
# Failure to yield
|
| 418 |
+
if road_type == 'roundabout' and ('roundabout' in v1_data['action'] or 'roundabout' in v2_data['action']):
|
| 419 |
+
factors.append('failure_to_yield')
|
| 420 |
+
elif road_type in ['crossroad', 't_junction']:
|
| 421 |
+
if random.random() < 0.4:
|
| 422 |
+
factors.append('failure_to_yield')
|
| 423 |
+
|
| 424 |
+
# Improper lane change
|
| 425 |
+
if 'changing_lane' in v1_data['action'] or 'changing_lane' in v2_data['action']:
|
| 426 |
+
factors.append('improper_lane_change')
|
| 427 |
+
|
| 428 |
+
# Signaling
|
| 429 |
+
if not v1_data['signaling'] and ('turn' in v1_data['action'] or 'changing' in v1_data['action']):
|
| 430 |
+
factors.append('failure_to_signal')
|
| 431 |
+
|
| 432 |
+
# Weather conditions
|
| 433 |
+
if weather in ['rainy', 'foggy', 'sandstorm']:
|
| 434 |
+
factors.append('weather_conditions')
|
| 435 |
+
|
| 436 |
+
# Road conditions
|
| 437 |
+
if road_condition != 'dry':
|
| 438 |
+
factors.append('road_conditions')
|
| 439 |
+
|
| 440 |
+
# Add some randomness
|
| 441 |
+
random_factors = ['distracted_driving', 'improper_turn', 'running_red_light', 'fatigue']
|
| 442 |
+
if random.random() < 0.3:
|
| 443 |
+
factors.append(random.choice(random_factors))
|
| 444 |
+
|
| 445 |
+
return factors[:4] # Limit to 4 factors
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def determine_fault(v1_data: Dict, v2_data: Dict, accident_type: str) -> int:
|
| 449 |
+
"""Determine which vehicle is primarily at fault."""
|
| 450 |
+
|
| 451 |
+
v1_score = 0
|
| 452 |
+
v2_score = 0
|
| 453 |
+
|
| 454 |
+
# Speed factor
|
| 455 |
+
if v1_data['speed_kmh'] > v2_data['speed_kmh']:
|
| 456 |
+
v1_score += 1
|
| 457 |
+
else:
|
| 458 |
+
v2_score += 1
|
| 459 |
+
|
| 460 |
+
# Signaling factor
|
| 461 |
+
if not v1_data['signaling']:
|
| 462 |
+
v1_score += 1
|
| 463 |
+
if not v2_data['signaling']:
|
| 464 |
+
v2_score += 1
|
| 465 |
+
|
| 466 |
+
# Braking factor (not braking is worse)
|
| 467 |
+
if not v1_data['braking']:
|
| 468 |
+
v1_score += 1
|
| 469 |
+
if not v2_data['braking']:
|
| 470 |
+
v2_score += 1
|
| 471 |
+
|
| 472 |
+
# Action-based fault
|
| 473 |
+
risky_actions = ['accelerating', 'changing_lane_left', 'changing_lane_right']
|
| 474 |
+
if v1_data['action'] in risky_actions:
|
| 475 |
+
v1_score += 1
|
| 476 |
+
if v2_data['action'] in risky_actions:
|
| 477 |
+
v2_score += 1
|
| 478 |
+
|
| 479 |
+
# Rear-end: usually rear vehicle at fault
|
| 480 |
+
if accident_type == 'rear_end_collision':
|
| 481 |
+
v2_score += 2
|
| 482 |
+
|
| 483 |
+
return 1 if v1_score > v2_score else 2
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def calculate_scenario_probability(
|
| 487 |
+
v1_data: Dict,
|
| 488 |
+
v2_data: Dict,
|
| 489 |
+
weather: str,
|
| 490 |
+
road_condition: str,
|
| 491 |
+
accident_type: str,
|
| 492 |
+
road_type: str = 'roundabout'
|
| 493 |
+
) -> float:
|
| 494 |
+
"""Calculate the probability of this accident scenario."""
|
| 495 |
+
|
| 496 |
+
base_prob = 0.5
|
| 497 |
+
|
| 498 |
+
# Road type risk factor
|
| 499 |
+
road_risk = {
|
| 500 |
+
'roundabout': 0.05, 'crossroad': 0.1, 't_junction': 0.08,
|
| 501 |
+
'highway_merge': 0.12, 'parking': -0.1, 'highway': 0.15,
|
| 502 |
+
'urban_road': 0.03, 'other': 0.05
|
| 503 |
+
}
|
| 504 |
+
base_prob += road_risk.get(road_type, 0.05)
|
| 505 |
+
|
| 506 |
+
# Collision angle impact
|
| 507 |
+
collision_angle = calculate_collision_angle(v1_data['direction'], v2_data['direction'])
|
| 508 |
+
if 60 < collision_angle < 120: # Side impact most likely at roundabout
|
| 509 |
+
base_prob += 0.15
|
| 510 |
+
elif collision_angle < 30: # Rear-end
|
| 511 |
+
base_prob += 0.1
|
| 512 |
+
|
| 513 |
+
# Speed impact
|
| 514 |
+
combined_speed = v1_data['speed_kmh'] + v2_data['speed_kmh']
|
| 515 |
+
if combined_speed > 100:
|
| 516 |
+
base_prob += 0.1
|
| 517 |
+
if combined_speed > 150:
|
| 518 |
+
base_prob += 0.1
|
| 519 |
+
|
| 520 |
+
# Weather impact
|
| 521 |
+
weather_impact = {
|
| 522 |
+
'clear': 0, 'cloudy': 0.02, 'rainy': 0.08,
|
| 523 |
+
'foggy': 0.1, 'sandstorm': 0.12
|
| 524 |
+
}
|
| 525 |
+
base_prob += weather_impact.get(weather, 0)
|
| 526 |
+
|
| 527 |
+
# Road condition impact
|
| 528 |
+
road_impact = {'dry': 0, 'wet': 0.08, 'sandy': 0.1, 'oily': 0.15}
|
| 529 |
+
base_prob += road_impact.get(road_condition, 0)
|
| 530 |
+
|
| 531 |
+
# Action risk
|
| 532 |
+
risky_actions = ['changing_lane_left', 'changing_lane_right', 'accelerating', 'entering_roundabout']
|
| 533 |
+
if v1_data['action'] in risky_actions:
|
| 534 |
+
base_prob += 0.05
|
| 535 |
+
if v2_data['action'] in risky_actions:
|
| 536 |
+
base_prob += 0.05
|
| 537 |
+
|
| 538 |
+
# Not braking increases risk
|
| 539 |
+
if not v1_data['braking'] and not v2_data['braking']:
|
| 540 |
+
base_prob += 0.05
|
| 541 |
+
|
| 542 |
+
# Add some randomness
|
| 543 |
+
base_prob += random.uniform(-0.1, 0.1)
|
| 544 |
+
|
| 545 |
+
return max(0.1, min(0.95, base_prob))
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
# ============================================================
|
| 549 |
+
# MAIN DATASET GENERATION
|
| 550 |
+
# ============================================================
|
| 551 |
+
|
| 552 |
+
def generate_single_accident() -> Dict:
|
| 553 |
+
"""Generate a single accident record."""
|
| 554 |
+
|
| 555 |
+
# Generate basic info
|
| 556 |
+
accident_id = generate_accident_id()
|
| 557 |
+
timestamp = generate_timestamp()
|
| 558 |
+
weather, visibility = select_weather()
|
| 559 |
+
road_condition = select_road_condition(weather)
|
| 560 |
+
|
| 561 |
+
# Select road type
|
| 562 |
+
road_type = random.choices(
|
| 563 |
+
list(ROAD_TYPE_WEIGHTS.keys()),
|
| 564 |
+
weights=list(ROAD_TYPE_WEIGHTS.values())
|
| 565 |
+
)[0]
|
| 566 |
+
|
| 567 |
+
# Determine lighting based on time
|
| 568 |
+
hour = timestamp.hour
|
| 569 |
+
if 7 <= hour < 17:
|
| 570 |
+
lighting = 'daylight'
|
| 571 |
+
elif hour in [6, 17, 18]:
|
| 572 |
+
lighting = random.choice(['dusk', 'dawn'])
|
| 573 |
+
elif 19 <= hour <= 23 or 0 <= hour < 6:
|
| 574 |
+
lighting = random.choice(['night_lit', 'night_dark'])
|
| 575 |
+
else:
|
| 576 |
+
lighting = 'daylight'
|
| 577 |
+
|
| 578 |
+
# Adjust visibility based on lighting
|
| 579 |
+
if lighting in ['night_dark']:
|
| 580 |
+
visibility = visibility * 0.6
|
| 581 |
+
elif lighting in ['night_lit']:
|
| 582 |
+
visibility = visibility * 0.8
|
| 583 |
+
elif lighting in ['dusk', 'dawn']:
|
| 584 |
+
visibility = visibility * 0.9
|
| 585 |
+
|
| 586 |
+
# Pre-select accident type for more realistic data
|
| 587 |
+
accident_type = random.choice(ACCIDENT_TYPES)
|
| 588 |
+
|
| 589 |
+
# Generate vehicle data
|
| 590 |
+
vehicle_1 = generate_vehicle_data(1, accident_type, road_type)
|
| 591 |
+
vehicle_2 = generate_vehicle_data(2, accident_type, road_type)
|
| 592 |
+
|
| 593 |
+
# Recalculate accident type based on actual vehicle data
|
| 594 |
+
actual_accident_type = determine_accident_type(
|
| 595 |
+
vehicle_1['direction'], vehicle_2['direction'],
|
| 596 |
+
vehicle_1['action'], vehicle_2['action'],
|
| 597 |
+
road_type
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
# Calculate collision details
|
| 601 |
+
collision_angle = calculate_collision_angle(
|
| 602 |
+
vehicle_1['direction'], vehicle_2['direction']
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
# Collision point (near center of roundabout)
|
| 606 |
+
collision_point = [
|
| 607 |
+
CASE_STUDY_LOCATION['latitude'] + random.uniform(-0.0005, 0.0005),
|
| 608 |
+
CASE_STUDY_LOCATION['longitude'] + random.uniform(-0.0005, 0.0005)
|
| 609 |
+
]
|
| 610 |
+
|
| 611 |
+
# Determine contributing factors
|
| 612 |
+
factors = determine_contributing_factors(
|
| 613 |
+
vehicle_1, vehicle_2, weather, road_condition, road_type
|
| 614 |
+
)
|
| 615 |
+
|
| 616 |
+
# Determine fault
|
| 617 |
+
fault_vehicle = determine_fault(vehicle_1, vehicle_2, actual_accident_type)
|
| 618 |
+
|
| 619 |
+
# Determine severity
|
| 620 |
+
combined_speed = vehicle_1['speed_kmh'] + vehicle_2['speed_kmh']
|
| 621 |
+
if combined_speed > 120:
|
| 622 |
+
severity = 'severe'
|
| 623 |
+
elif combined_speed > 80:
|
| 624 |
+
severity = 'moderate'
|
| 625 |
+
else:
|
| 626 |
+
severity = 'minor'
|
| 627 |
+
|
| 628 |
+
# Calculate probability
|
| 629 |
+
probability = calculate_scenario_probability(
|
| 630 |
+
vehicle_1, vehicle_2, weather, road_condition, actual_accident_type, road_type
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
return {
|
| 634 |
+
'accident_id': accident_id,
|
| 635 |
+
'timestamp': timestamp.isoformat(),
|
| 636 |
+
'location': {
|
| 637 |
+
'name': CASE_STUDY_LOCATION['name'],
|
| 638 |
+
'latitude': CASE_STUDY_LOCATION['latitude'],
|
| 639 |
+
'longitude': CASE_STUDY_LOCATION['longitude'],
|
| 640 |
+
'road_type': road_type
|
| 641 |
+
},
|
| 642 |
+
'conditions': {
|
| 643 |
+
'weather': weather,
|
| 644 |
+
'road_condition': road_condition,
|
| 645 |
+
'visibility': round(visibility, 2),
|
| 646 |
+
'lighting': lighting
|
| 647 |
+
},
|
| 648 |
+
'vehicle_1': vehicle_1,
|
| 649 |
+
'vehicle_2': vehicle_2,
|
| 650 |
+
'accident_details': {
|
| 651 |
+
'type': actual_accident_type,
|
| 652 |
+
'severity': severity,
|
| 653 |
+
'collision_angle': collision_angle,
|
| 654 |
+
'collision_point': collision_point,
|
| 655 |
+
'contributing_factors': factors,
|
| 656 |
+
'fault_vehicle': fault_vehicle
|
| 657 |
+
},
|
| 658 |
+
'outcomes': {
|
| 659 |
+
'scenario_probability': round(probability, 3),
|
| 660 |
+
'damage_estimate': severity,
|
| 661 |
+
'injuries': severity in ['moderate', 'severe'] and random.random() > 0.4
|
| 662 |
+
}
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
def generate_dataset(num_samples: int = 1000) -> pd.DataFrame:
|
| 667 |
+
"""Generate a complete synthetic accident dataset."""
|
| 668 |
+
|
| 669 |
+
print(f"Generating {num_samples} synthetic accident records...")
|
| 670 |
+
|
| 671 |
+
accidents = []
|
| 672 |
+
for i in range(num_samples):
|
| 673 |
+
if (i + 1) % 1000 == 0:
|
| 674 |
+
print(f" Generated {i + 1}/{num_samples} records...")
|
| 675 |
+
|
| 676 |
+
accident = generate_single_accident()
|
| 677 |
+
|
| 678 |
+
# Flatten for DataFrame
|
| 679 |
+
flat_record = {
|
| 680 |
+
'accident_id': accident['accident_id'],
|
| 681 |
+
'timestamp': accident['timestamp'],
|
| 682 |
+
'location_name': accident['location']['name'],
|
| 683 |
+
'latitude': accident['location']['latitude'],
|
| 684 |
+
'longitude': accident['location']['longitude'],
|
| 685 |
+
'road_type': accident['location']['road_type'],
|
| 686 |
+
'weather': accident['conditions']['weather'],
|
| 687 |
+
'road_condition': accident['conditions']['road_condition'],
|
| 688 |
+
'visibility': accident['conditions']['visibility'],
|
| 689 |
+
'lighting': accident['conditions']['lighting'],
|
| 690 |
+
|
| 691 |
+
# Vehicle 1
|
| 692 |
+
'v1_type': accident['vehicle_1']['type'],
|
| 693 |
+
'v1_speed': accident['vehicle_1']['speed_kmh'],
|
| 694 |
+
'v1_direction': accident['vehicle_1']['direction'],
|
| 695 |
+
'v1_direction_angle': accident['vehicle_1']['direction_angle'],
|
| 696 |
+
'v1_action': accident['vehicle_1']['action'],
|
| 697 |
+
'v1_braking': accident['vehicle_1']['braking'],
|
| 698 |
+
'v1_signaling': accident['vehicle_1']['signaling'],
|
| 699 |
+
|
| 700 |
+
# Vehicle 2
|
| 701 |
+
'v2_type': accident['vehicle_2']['type'],
|
| 702 |
+
'v2_speed': accident['vehicle_2']['speed_kmh'],
|
| 703 |
+
'v2_direction': accident['vehicle_2']['direction'],
|
| 704 |
+
'v2_direction_angle': accident['vehicle_2']['direction_angle'],
|
| 705 |
+
'v2_action': accident['vehicle_2']['action'],
|
| 706 |
+
'v2_braking': accident['vehicle_2']['braking'],
|
| 707 |
+
'v2_signaling': accident['vehicle_2']['signaling'],
|
| 708 |
+
|
| 709 |
+
# Accident details
|
| 710 |
+
'accident_type': accident['accident_details']['type'],
|
| 711 |
+
'severity': accident['accident_details']['severity'],
|
| 712 |
+
'collision_angle': accident['accident_details']['collision_angle'],
|
| 713 |
+
'contributing_factors': ','.join(accident['accident_details']['contributing_factors']),
|
| 714 |
+
'fault_vehicle': accident['accident_details']['fault_vehicle'],
|
| 715 |
+
|
| 716 |
+
# Outcomes
|
| 717 |
+
'scenario_probability': accident['outcomes']['scenario_probability'],
|
| 718 |
+
'injuries': accident['outcomes']['injuries']
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
+
accidents.append(flat_record)
|
| 722 |
+
|
| 723 |
+
df = pd.DataFrame(accidents)
|
| 724 |
+
print(f"Dataset generated with {len(df)} records.")
|
| 725 |
+
|
| 726 |
+
return df
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
def generate_training_features(df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray]:
|
| 730 |
+
"""
|
| 731 |
+
Convert dataset to feature vectors for MindSpore training.
|
| 732 |
+
|
| 733 |
+
Features (32 total):
|
| 734 |
+
- Vehicle 1: type, speed, direction, angle, action, braking, signaling (7)
|
| 735 |
+
- Vehicle 2: type, speed, direction, angle, action, braking, signaling (7)
|
| 736 |
+
- Environment: weather, road_condition, visibility, lighting, road_type (5)
|
| 737 |
+
- Derived: collision_angle, speed_diff, combined_speed, same_direction,
|
| 738 |
+
speed_product, angle_diff, time_of_day, risk_score,
|
| 739 |
+
v1_action_risk, v2_action_risk, relative_speed, approach_rate (12)
|
| 740 |
+
- Total: 31 input features
|
| 741 |
+
|
| 742 |
+
Returns:
|
| 743 |
+
X: Feature matrix (N x 31)
|
| 744 |
+
y: Labels - accident type encoded (N,)
|
| 745 |
+
"""
|
| 746 |
+
|
| 747 |
+
# Encode categorical variables
|
| 748 |
+
direction_encoding = {d: i for i, d in enumerate(DIRECTIONS.keys())}
|
| 749 |
+
action_encoding = {a: i for i, a in enumerate(VEHICLE_ACTIONS)}
|
| 750 |
+
vehicle_encoding = {v: i for i, v in enumerate(VEHICLE_TYPES.keys())}
|
| 751 |
+
weather_encoding = {'clear': 0, 'cloudy': 1, 'rainy': 2, 'foggy': 3, 'sandstorm': 4}
|
| 752 |
+
road_encoding = {'dry': 0, 'wet': 1, 'sandy': 2, 'oily': 3}
|
| 753 |
+
road_type_encoding = {
|
| 754 |
+
'roundabout': 0, 'crossroad': 1, 't_junction': 2, 'highway_merge': 3,
|
| 755 |
+
'parking': 4, 'highway': 5, 'urban_road': 6, 'other': 7
|
| 756 |
+
}
|
| 757 |
+
lighting_encoding = {'daylight': 0, 'dusk': 1, 'dawn': 2, 'night_lit': 3, 'night_dark': 4}
|
| 758 |
+
accident_encoding = {a: i for i, a in enumerate(ACCIDENT_TYPES)}
|
| 759 |
+
|
| 760 |
+
# Action risk scores
|
| 761 |
+
action_risk = {
|
| 762 |
+
'going_straight': 0.3, 'turning_left': 0.5, 'turning_right': 0.4,
|
| 763 |
+
'entering_roundabout': 0.6, 'exiting_roundabout': 0.5,
|
| 764 |
+
'changing_lane_left': 0.7, 'changing_lane_right': 0.7,
|
| 765 |
+
'slowing_down': 0.4, 'accelerating': 0.6, 'stopped': 0.2
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
features = []
|
| 769 |
+
labels = []
|
| 770 |
+
|
| 771 |
+
for _, row in df.iterrows():
|
| 772 |
+
# Extract time of day (hour) from timestamp if available
|
| 773 |
+
try:
|
| 774 |
+
hour = pd.to_datetime(row['timestamp']).hour
|
| 775 |
+
except:
|
| 776 |
+
hour = 12 # Default to noon
|
| 777 |
+
|
| 778 |
+
# Calculate derived features
|
| 779 |
+
v1_speed = row['v1_speed']
|
| 780 |
+
v2_speed = row['v2_speed']
|
| 781 |
+
v1_angle = row['v1_direction_angle']
|
| 782 |
+
v2_angle = row['v2_direction_angle']
|
| 783 |
+
collision_angle = row['collision_angle']
|
| 784 |
+
|
| 785 |
+
speed_diff = abs(v1_speed - v2_speed)
|
| 786 |
+
combined_speed = v1_speed + v2_speed
|
| 787 |
+
same_direction = 1 if row['v1_direction'] == row['v2_direction'] else 0
|
| 788 |
+
speed_product = v1_speed * v2_speed
|
| 789 |
+
angle_diff = (v1_angle - v2_angle) % 360
|
| 790 |
+
if angle_diff > 180:
|
| 791 |
+
angle_diff = 360 - angle_diff
|
| 792 |
+
|
| 793 |
+
# Risk score based on conditions
|
| 794 |
+
weather_risk = {'clear': 0.1, 'cloudy': 0.2, 'rainy': 0.5, 'foggy': 0.7, 'sandstorm': 0.8}
|
| 795 |
+
road_risk = {'dry': 0.1, 'wet': 0.5, 'sandy': 0.6, 'oily': 0.8}
|
| 796 |
+
base_risk = weather_risk.get(row['weather'], 0.3) + road_risk.get(row['road_condition'], 0.3)
|
| 797 |
+
|
| 798 |
+
# Relative speed (closing speed)
|
| 799 |
+
if angle_diff > 90: # Approaching
|
| 800 |
+
relative_speed = v1_speed + v2_speed
|
| 801 |
+
else: # Same direction
|
| 802 |
+
relative_speed = abs(v1_speed - v2_speed)
|
| 803 |
+
|
| 804 |
+
# Approach rate (how quickly vehicles are approaching collision)
|
| 805 |
+
approach_rate = relative_speed * (1 - row['visibility']) * (1 + base_risk)
|
| 806 |
+
|
| 807 |
+
feature_vector = [
|
| 808 |
+
# Vehicle 1 features (7)
|
| 809 |
+
vehicle_encoding.get(row['v1_type'], 0) / 5,
|
| 810 |
+
v1_speed / 200, # Normalize speed
|
| 811 |
+
direction_encoding.get(row['v1_direction'], 0) / 8,
|
| 812 |
+
v1_angle / 360,
|
| 813 |
+
action_encoding.get(row['v1_action'], 0) / 10,
|
| 814 |
+
1 if row['v1_braking'] else 0,
|
| 815 |
+
1 if row['v1_signaling'] else 0,
|
| 816 |
+
|
| 817 |
+
# Vehicle 2 features (7)
|
| 818 |
+
vehicle_encoding.get(row['v2_type'], 0) / 5,
|
| 819 |
+
v2_speed / 200,
|
| 820 |
+
direction_encoding.get(row['v2_direction'], 0) / 8,
|
| 821 |
+
v2_angle / 360,
|
| 822 |
+
action_encoding.get(row['v2_action'], 0) / 10,
|
| 823 |
+
1 if row['v2_braking'] else 0,
|
| 824 |
+
1 if row['v2_signaling'] else 0,
|
| 825 |
+
|
| 826 |
+
# Environmental features (5)
|
| 827 |
+
weather_encoding.get(row['weather'], 0) / 5,
|
| 828 |
+
road_encoding.get(row['road_condition'], 0) / 4,
|
| 829 |
+
row['visibility'],
|
| 830 |
+
lighting_encoding.get(row.get('lighting', 'daylight'), 0) / 5,
|
| 831 |
+
road_type_encoding.get(row.get('road_type', 'roundabout'), 0) / 8,
|
| 832 |
+
|
| 833 |
+
# Derived features (12)
|
| 834 |
+
collision_angle / 180,
|
| 835 |
+
speed_diff / 200,
|
| 836 |
+
combined_speed / 400,
|
| 837 |
+
same_direction,
|
| 838 |
+
speed_product / 40000,
|
| 839 |
+
angle_diff / 180,
|
| 840 |
+
hour / 24, # Time of day
|
| 841 |
+
base_risk, # Risk score
|
| 842 |
+
action_risk.get(row['v1_action'], 0.5), # V1 action risk
|
| 843 |
+
action_risk.get(row['v2_action'], 0.5), # V2 action risk
|
| 844 |
+
relative_speed / 400, # Relative/closing speed
|
| 845 |
+
min(approach_rate / 200, 1.0), # Approach rate (capped)
|
| 846 |
+
]
|
| 847 |
+
|
| 848 |
+
features.append(feature_vector)
|
| 849 |
+
labels.append(accident_encoding.get(row['accident_type'], 0))
|
| 850 |
+
|
| 851 |
+
X = np.array(features, dtype=np.float32)
|
| 852 |
+
y = np.array(labels, dtype=np.int32)
|
| 853 |
+
|
| 854 |
+
return X, y
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
def generate_training_features_extended(df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 858 |
+
"""
|
| 859 |
+
Extended feature generation that also outputs probability targets.
|
| 860 |
+
|
| 861 |
+
Returns:
|
| 862 |
+
X: Feature matrix
|
| 863 |
+
y_class: Classification labels (accident type)
|
| 864 |
+
y_prob: Probability targets (for regression)
|
| 865 |
+
"""
|
| 866 |
+
X, y_class = generate_training_features(df)
|
| 867 |
+
y_prob = df['scenario_probability'].values.astype(np.float32)
|
| 868 |
+
|
| 869 |
+
return X, y_class, y_prob
|
| 870 |
+
|
| 871 |
+
|
| 872 |
+
def save_dataset(df: pd.DataFrame, filename: str = "synthetic_accidents"):
|
| 873 |
+
"""Save the dataset in multiple formats."""
|
| 874 |
+
|
| 875 |
+
# Create directories if needed
|
| 876 |
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
| 877 |
+
PROCESSED_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
| 878 |
+
|
| 879 |
+
# Save as CSV
|
| 880 |
+
csv_path = PROCESSED_DATA_DIR / f"{filename}.csv"
|
| 881 |
+
df.to_csv(csv_path, index=False)
|
| 882 |
+
print(f"Saved CSV: {csv_path}")
|
| 883 |
+
|
| 884 |
+
# Save as JSON (full records)
|
| 885 |
+
json_path = PROCESSED_DATA_DIR / f"{filename}.json"
|
| 886 |
+
df.to_json(json_path, orient='records', indent=2)
|
| 887 |
+
print(f"Saved JSON: {json_path}")
|
| 888 |
+
|
| 889 |
+
# Generate and save training features
|
| 890 |
+
X, y = generate_training_features(df)
|
| 891 |
+
|
| 892 |
+
np_path = PROCESSED_DATA_DIR / f"{filename}_features.npz"
|
| 893 |
+
np.savez(np_path, X=X, y=y)
|
| 894 |
+
print(f"Saved NumPy features: {np_path}")
|
| 895 |
+
|
| 896 |
+
# Save schema
|
| 897 |
+
schema_path = DATA_DIR / "accident_schema.json"
|
| 898 |
+
with open(schema_path, 'w') as f:
|
| 899 |
+
json.dump(ACCIDENT_SCHEMA, f, indent=2)
|
| 900 |
+
print(f"Saved schema: {schema_path}")
|
| 901 |
+
|
| 902 |
+
return csv_path, json_path, np_path
|
| 903 |
+
|
| 904 |
+
|
| 905 |
+
def print_dataset_statistics(df: pd.DataFrame):
|
| 906 |
+
"""Print statistics about the generated dataset."""
|
| 907 |
+
|
| 908 |
+
print("\n" + "="*60)
|
| 909 |
+
print("DATASET STATISTICS")
|
| 910 |
+
print("="*60)
|
| 911 |
+
|
| 912 |
+
print(f"\nTotal records: {len(df)}")
|
| 913 |
+
|
| 914 |
+
print(f"\n--- Accident Types ---")
|
| 915 |
+
print(df['accident_type'].value_counts())
|
| 916 |
+
|
| 917 |
+
print(f"\n--- Weather Conditions ---")
|
| 918 |
+
print(df['weather'].value_counts())
|
| 919 |
+
|
| 920 |
+
print(f"\n--- Road Conditions ---")
|
| 921 |
+
print(df['road_condition'].value_counts())
|
| 922 |
+
|
| 923 |
+
print(f"\n--- Severity Distribution ---")
|
| 924 |
+
print(df['severity'].value_counts())
|
| 925 |
+
|
| 926 |
+
print(f"\n--- Vehicle Types (V1) ---")
|
| 927 |
+
print(df['v1_type'].value_counts())
|
| 928 |
+
|
| 929 |
+
print(f"\n--- Speed Statistics ---")
|
| 930 |
+
print(f"V1 Speed: Mean={df['v1_speed'].mean():.1f}, Std={df['v1_speed'].std():.1f}")
|
| 931 |
+
print(f"V2 Speed: Mean={df['v2_speed'].mean():.1f}, Std={df['v2_speed'].std():.1f}")
|
| 932 |
+
|
| 933 |
+
print(f"\n--- Fault Distribution ---")
|
| 934 |
+
print(df['fault_vehicle'].value_counts())
|
| 935 |
+
|
| 936 |
+
print(f"\n--- Injuries ---")
|
| 937 |
+
print(df['injuries'].value_counts())
|
| 938 |
+
|
| 939 |
+
print("\n" + "="*60)
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
# ============================================================
|
| 943 |
+
# MAIN EXECUTION
|
| 944 |
+
# ============================================================
|
| 945 |
+
|
| 946 |
+
if __name__ == "__main__":
|
| 947 |
+
print("="*60)
|
| 948 |
+
print("SYNTHETIC ACCIDENT DATASET GENERATOR")
|
| 949 |
+
print("Huawei AI Innovation Challenge 2026")
|
| 950 |
+
print("="*60)
|
| 951 |
+
|
| 952 |
+
# Generate dataset
|
| 953 |
+
df = generate_dataset(num_samples=1000)
|
| 954 |
+
|
| 955 |
+
# Print statistics
|
| 956 |
+
print_dataset_statistics(df)
|
| 957 |
+
|
| 958 |
+
# Save dataset
|
| 959 |
+
csv_path, json_path, np_path = save_dataset(df)
|
| 960 |
+
|
| 961 |
+
print("\n" + "="*60)
|
| 962 |
+
print("DATASET GENERATION COMPLETE!")
|
| 963 |
+
print("="*60)
|
| 964 |
+
print(f"\nFiles saved:")
|
| 965 |
+
print(f" - {csv_path}")
|
| 966 |
+
print(f" - {json_path}")
|
| 967 |
+
print(f" - {np_path}")
|