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[]; created_at?: string; }
|
export interface DetectionCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; createdAt: string; run: DetectionRun; images: DetectionImage[]; }
|
export interface TrajectoryEvent { event_id: string; event_type: string; track_ids: string[]; start_time: string; end_time: string; duration_seconds: number; }
|
export interface TrajectoryCaseRun { case_id: string; input_count: number; track_count: number; event_count: number; dropped_duplicate_observations: number; elapsed_seconds: number; device: string; input: string; thresholds: Record<string, number>; created_at?: string; }
|
export interface TrajectorySummary { track_id: string; entity_type: string; point_count: string; distance_m: string; average_speed_mps: string; behavior_labels: string; }
|
export interface TrajectoryCase { id: string; label: string; note: string; artifactRoot: string; showSpatialContext: boolean; showFlyableZones: boolean; createdAt: string; run: TrajectoryCaseRun; events: TrajectoryEvent[]; summary: TrajectorySummary[]; }
|
interface CaseDefinition { id: string; label: string; note: string; artifactRoot: string; createdAt: string; }
|
interface TrajectoryDefinition extends CaseDefinition { showSpatialContext: boolean; showFlyableZones: boolean; }
|
interface DetectionDefinition extends CaseDefinition { inputRoot: string; }
|
export interface UploadFilePayload { name: string; content: string; }
|
|
export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`;
|
|
async function responseJson<T>(response: Response): Promise<T> {
|
const payload = await response.json().catch(() => ({})) as { error?: string } & T;
|
if (!response.ok) throw new Error(payload.error || `本地服务请求失败 (${response.status})`);
|
return payload;
|
}
|
async function getJson<T>(path: string): Promise<T> { return responseJson<T>(await fetch(artifactUrl(path), { cache: "no-store" })); }
|
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 loadTrajectoryArtifacts(): Promise<Record<string, TrajectoryCase>> {
|
const { runs } = await getJson<{ runs: TrajectoryDefinition[] }>("api/trajectory/runs");
|
const cases = await Promise.all(runs.map(async (definition) => {
|
const [run, eventPayload, summary] = await Promise.all([getJson<TrajectoryCaseRun>(`${definition.artifactRoot}/run_metadata.json`), getJson<{ events: TrajectoryEvent[] }>(`${definition.artifactRoot}/events.json`), getText(`${definition.artifactRoot}/trajectory_summary.csv`)]);
|
return { ...definition, run, events: eventPayload.events, summary: parseCsv(summary) } satisfies TrajectoryCase;
|
}));
|
return Object.fromEntries(cases.map((item) => [item.id, item]));
|
}
|
|
export async function loadDetectionArtifacts(): Promise<Record<string, DetectionCase>> {
|
const { runs } = await getJson<{ runs: DetectionDefinition[] }>("api/object-detection/runs");
|
const cases = await Promise.all(runs.map(async (definition) => {
|
const [run, payload] = await Promise.all([getJson<DetectionRun>(`${definition.artifactRoot}/run_metadata.json`), getJson<{ images: DetectionImage[] }>(`${definition.artifactRoot}/detections.json`)]);
|
return { ...definition, run, images: payload.images } satisfies DetectionCase;
|
}));
|
return Object.fromEntries(cases.map((item) => [item.id, item]));
|
}
|
|
async function postRun<T>(path: string, body: object): Promise<T> { return responseJson<T>(await fetch(artifactUrl(path), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })); }
|
export async function createTrajectoryRun(files: { flight: UploadFilePayload; route: UploadFilePayload; restricted: UploadFilePayload; flyable?: UploadFilePayload }) { return postRun<{ run: TrajectoryDefinition }>("api/trajectory/runs", { files }); }
|
export async function createDetectionRun(images: UploadFilePayload[]) { return postRun<{ run: DetectionDefinition }>("api/object-detection/runs", { images }); }
|
export function readFileAsPayload(file: File): Promise<UploadFilePayload> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(new Error(`无法读取 ${file.name}`)); reader.onload = () => { const value = String(reader.result ?? ""); resolve({ name: file.name, content: value.slice(value.indexOf(",") + 1) }); }; reader.readAsDataURL(file); }); }
|