docs: trace heatmap spec v1.0.0 — spatial + temporal + combined
This commit is contained in:
183
docs_v1.0/API_V1.0.0/TRACE/TRACE_HEATMAP_SPEC_V1.0.0.md
Normal file
183
docs_v1.0/API_V1.0.0/TRACE/TRACE_HEATMAP_SPEC_V1.0.0.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Trace Heatmap Specification v1.0.0
|
||||
|
||||
## Concept
|
||||
|
||||
將臉部追蹤資料標準化為熱力圖格式,包含空間(畫面位置)和時間(影片進度)兩個維度。
|
||||
|
||||
## Data Model
|
||||
|
||||
### 1. Spatial Heatmap — 臉在哪裡?
|
||||
|
||||
將畫面划分為 grid,計算每個 cell 的 face 活動量:
|
||||
|
||||
```
|
||||
frame (1920×1080)
|
||||
┌──────────────────────┐
|
||||
│ ░░ ░░ ░░░░ │ y
|
||||
│ ░░░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░ │ ← 活動集中在畫面中央
|
||||
│ ░░ │
|
||||
└──────────────────────┘
|
||||
x →
|
||||
```
|
||||
|
||||
### 2. Temporal Heatmap — 什麼時候有臉?
|
||||
|
||||
```
|
||||
face_density
|
||||
│ ██ ████ ██
|
||||
│ ██ ██ ████ ██ ██
|
||||
│ ██ ██ ██████ ██ ██
|
||||
└──────────────────────────→ time
|
||||
0s 3000s 6000s
|
||||
```
|
||||
|
||||
## API Design
|
||||
|
||||
### GET /api/v1/file/:file_uuid/face_trace/heatmap
|
||||
|
||||
回傳標準化熱力圖資料。
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `type` | enum | `spatial` | `spatial` \| `temporal` \| `combined` |
|
||||
| `grid_x` | int | 20 | Spatial: X axis grid cells |
|
||||
| `grid_y` | int | 12 | Spatial: Y axis grid cells |
|
||||
| `bin_sec` | int | 60 | Temporal: time bucket in seconds |
|
||||
| `time_start` | float | 0 | Time range start (seconds) |
|
||||
| `time_end` | float | — | Time range end (default: video duration) |
|
||||
| `min_confidence` | float | 0.5 | Minimum detection confidence |
|
||||
| `trace_ids` | string | — | Comma-separated trace_id filter (optional) |
|
||||
|
||||
#### Response: Spatial
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "spatial",
|
||||
"frame_width": 1920,
|
||||
"frame_height": 1080,
|
||||
"grid_x": 20,
|
||||
"grid_y": 12,
|
||||
"cell_w": 96,
|
||||
"cell_h": 90,
|
||||
"data": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 3, 5, 8, 12, 14, 10, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 4, 8, 15, 25, 35, 40, 32, 20, 10, 4, 1, 0, 0, 0, 0, 0],
|
||||
...
|
||||
],
|
||||
"max_value": 40,
|
||||
"total_detections": 108204,
|
||||
"total_traces": 6892
|
||||
}
|
||||
```
|
||||
|
||||
#### Response: Temporal
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "temporal",
|
||||
"bin_sec": 60,
|
||||
"duration": 5954,
|
||||
"bins": [
|
||||
{"t_start": 0, "t_end": 60, "face_count": 45, "trace_count": 12, "avg_confidence": 0.82},
|
||||
{"t_start": 60, "t_end": 120, "face_count": 120, "trace_count": 28, "avg_confidence": 0.79},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Response: Combined
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "combined",
|
||||
"bin_sec": 60,
|
||||
"grid_x": 20,
|
||||
"grid_y": 12,
|
||||
"duration": 5954,
|
||||
"data": [
|
||||
{
|
||||
"t_start": 0,
|
||||
"t_end": 60,
|
||||
"heatmap": [[...], [...]],
|
||||
"max_cell": 8,
|
||||
"face_count": 45
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Standardization Rules
|
||||
|
||||
1. **Normalization**: All values normalized to 0.0–1.0 range with `max_value` for rendering
|
||||
2. **Resolution independence**: Grid size configurable, always returns `cell_w`/`cell_h`
|
||||
3. **Temporal bucketing**: Default 60s bins, adjustable
|
||||
4. **Filterable**: By time range, confidence, specific traces
|
||||
5. **Cachable**: Response includes `file_uuid` + query hash for CDN/Redis cache
|
||||
|
||||
## Frontend Rendering
|
||||
|
||||
### Spatial Heatmap (Canvas)
|
||||
|
||||
```javascript
|
||||
function renderHeatmap(canvas, data) {
|
||||
const cellW = canvas.width / data.grid_x;
|
||||
const cellH = canvas.height / data.grid_y;
|
||||
for (let y = 0; y < data.grid_y; y++) {
|
||||
for (let x = 0; x < data.grid_x; x++) {
|
||||
const intensity = data.data[y][x] / data.max_value;
|
||||
ctx.fillStyle = `rgba(255, 0, 0, ${intensity})`;
|
||||
ctx.fillRect(x * cellW, y * cellH, cellW, cellH);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Temporal Heatmap (SVG)
|
||||
|
||||
```svg
|
||||
<rect x="0" y="0" width="10" height="100" fill="rgba(255,0,0,0.1)" />
|
||||
<rect x="10" y="0" width="10" height="100" fill="rgba(255,0,0,0.3)" />
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Backend (src/api/trace_agent_api.rs)
|
||||
|
||||
Add new route:
|
||||
```rust
|
||||
.route("/api/v1/file/:file_uuid/face_trace/heatmap", get(list_trace_heatmap))
|
||||
```
|
||||
|
||||
### SQL (spatial)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
FLOOR(x / $grid_w) AS cell_x,
|
||||
FLOOR(y / $grid_h) AS cell_y,
|
||||
COUNT(*) AS intensity
|
||||
FROM dev.face_detections
|
||||
WHERE file_uuid = $1 AND confidence >= $2
|
||||
GROUP BY cell_x, cell_y
|
||||
ORDER BY cell_x, cell_y;
|
||||
```
|
||||
|
||||
### SQL (temporal)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
FLOOR(frame_number / ($fps * $bin_sec)) AS bin,
|
||||
COUNT(*) AS face_count,
|
||||
COUNT(DISTINCT trace_id) AS trace_count,
|
||||
AVG(confidence) AS avg_confidence
|
||||
FROM dev.face_detections
|
||||
WHERE file_uuid = $1 AND confidence >= $2
|
||||
GROUP BY bin
|
||||
ORDER BY bin;
|
||||
```
|
||||
Reference in New Issue
Block a user