shuishen
5 days ago f67d43a43f6c3e58922e3c2f457dd9caafebfc67
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
export interface Detection {
  class_id: number;
  class_name: string;
  confidence: number;
  bbox_xyxy: number[];
}
 
export interface DetectionImage {
  file: string;
  annotated_file: string;
  width: number;
  height: number;
  detections: Detection[];
}
 
export interface DetectionRun {
  detection_count: number;
  processed_images: number;
  elapsed_seconds: number;
  device: string;
  model: string;
  confidence: number;
  input_dir: string;
  notes: string[];
}
 
export interface TrajectoryEvent {
  event_id: string;
  event_type: string;
  track_ids: string[];
  start_time: string;
  end_time: string;
  duration_seconds: number;
}
 
export interface TrajectoryRun {
  case_count: number;
  track_count: number;
  event_count: number;
  dropped_duplicate_observations: number;
  elapsed_seconds: number;
  device: string;
  input: string;
  thresholds_by_case: Record<string, Record<string, number>>;
}
 
export interface TrajectorySummary {
  track_id: string;
  entity_type: string;
  point_count: string;
  distance_m: string;
  average_speed_mps: string;
  behavior_labels: string;
}
 
export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`;
 
async function getJson<T>(path: string): Promise<T> {
  const response = await fetch(artifactUrl(path), { cache: "no-store" });
  if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`);
  return response.json() as Promise<T>;
}
 
async function getText(path: string): Promise<string> {
  const response = await fetch(artifactUrl(path), { cache: "no-store" });
  if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`);
  return response.text();
}
 
function parseCsv(text: string): TrajectorySummary[] {
  const lines = text.trim().split(/\r?\n/);
  const headers = lines.shift()?.replace(/^\uFEFF/, "").split(",") ?? [];
  return lines.filter(Boolean).map((line) => {
    const values = line.split(",");
    return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""])) as unknown as TrajectorySummary;
  });
}
 
export async function loadDetectionArtifacts() {
  const [run, payload] = await Promise.all([
    getJson<DetectionRun>("shared/outputs/01-object-detection/run_metadata.json"),
    getJson<{ images: DetectionImage[] }>("shared/outputs/01-object-detection/detections.json")
  ]);
  return { run, images: payload.images };
}
 
export async function loadTrajectoryArtifacts() {
  const [run, difficultEvents, difficultSummary, normalSummary] = await Promise.all([
    getJson<TrajectoryRun>("shared/outputs/15-trajectory-analysis/run_metadata.json"),
    getJson<{ events: TrajectoryEvent[] }>("shared/outputs/15-trajectory-analysis/difficult/events.json"),
    getText("shared/outputs/15-trajectory-analysis/difficult/trajectory_summary.csv"),
    getText("shared/outputs/15-trajectory-analysis/normal/trajectory_summary.csv")
  ]);
  return {
    run,
    cases: {
      difficult: { events: difficultEvents.events, summary: parseCsv(difficultSummary) },
      normal: { events: [], summary: parseCsv(normalSummary) }
    }
  };
}