const API_BASE = window.location.hostname === 'localhost' ? 'http://localhost:8000' : ''; export interface ClassificationResult { label: string; confidence: number; } export interface PreprocessingInfo { type: 'dicom' | 'image'; pipeline: 'full' | 'basic'; steps_applied: string[]; processed_image_base64?: string; metadata: { original_size?: [number, number]; processed_size?: [number, number]; pixel_spacing?: number; fan_size?: [number, number]; original_pixel_spacing?: [number, number]; }; } export interface ClassificationResponse { success: boolean; predictions: ClassificationResult[]; top_prediction: ClassificationResult | null; preprocessing: PreprocessingInfo; } export interface GestationalAgeResponse { success: boolean; gestational_age: { weeks: number; days: number; total_days: number; }; // Biometry - only one will be present based on view head_circumference?: { p2_5: number; p50: number; p97_5: number; }; abdominal_circumference?: { p2_5: number; p50: number; p97_5: number; }; femur_length?: { p2_5: number; p50: number; p97_5: number; }; preprocessing: PreprocessingInfo; } export async function classifyImage(file: File, topK: number = 5): Promise { const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE}/api/v1/classify/?top_k=${topK}`, { method: 'POST', body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Classification failed'); } return response.json(); } export async function estimateGestationalAge(file: File, pixelSize: number, view?: string): Promise { const formData = new FormData(); formData.append('file', file); let url = `${API_BASE}/api/v1/gestational-age/?pixel_size=${pixelSize}`; if (view) { url += `&view=${encodeURIComponent(view)}`; } const response = await fetch(url, { method: 'POST', body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Estimation failed'); } return response.json(); } export function isDicomFile(filename: string): boolean { const lower = filename.toLowerCase(); return lower.endsWith('.dcm') || lower.endsWith('.dicom'); } export function getFileType(filename: string): 'dicom' | 'image' { return isDicomFile(filename) ? 'dicom' : 'image'; } export interface PreviewResponse { success: boolean; preview: string; type: 'dicom' | 'image'; mime_type?: string; } export async function getFilePreview(file: File): Promise { const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE}/api/v1/classify/preview`, { method: 'POST', body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Preview failed'); } return response.json(); } export async function checkHealth(): Promise { try { const response = await fetch(`${API_BASE}/health`, { method: 'GET' }); return response.ok; } catch { return false; } } // ==================== Feedback API ==================== export interface PredictionDetail { label: string; probability: number; } export interface FeedbackEntry { id: string; session_id: string; timestamp: string; filename: string; file_type: string; patient_id: string | null; image_hash: string | null; predicted_label: string; predicted_confidence: number; all_predictions: PredictionDetail[]; is_correct: boolean | null; correct_label: string | null; reviewer_notes: string | null; } export interface SessionInfo { session_id: string; created_at: string; image_count: number; feedback_count: number; correct_count: number; incorrect_count: number; } export interface FeedbackStats { total_feedback: number; correct_count: number; incorrect_count: number; not_sure_count: number; accuracy: number; by_label: Record; } export interface FeedbackCreate { session_id: string; filename: string; file_type: string; predicted_label: string; predicted_confidence: number; all_predictions: PredictionDetail[]; is_correct: boolean | null; correct_label?: string; reviewer_notes?: string; patient_id?: string; image_hash?: string; preprocessed_image_base64?: string; } // Create a new session export async function createSession(): Promise { const response = await fetch(`${API_BASE}/api/v1/feedback/session`, { method: 'POST', }); if (!response.ok) { throw new Error('Failed to create session'); } return response.json(); } // Get session info export async function getSession(sessionId: string): Promise { const response = await fetch(`${API_BASE}/api/v1/feedback/session/${sessionId}`); if (!response.ok) { throw new Error('Session not found'); } return response.json(); } // Record image analyzed export async function recordImageAnalyzed(sessionId: string): Promise { await fetch(`${API_BASE}/api/v1/feedback/session/${sessionId}/image-analyzed`, { method: 'POST', }); } // Submit feedback export async function submitFeedback(feedback: FeedbackCreate): Promise { const response = await fetch(`${API_BASE}/api/v1/feedback/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(feedback), }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to submit feedback'); } return response.json(); } // Get all feedback export async function getFeedback(sessionId?: string): Promise { const url = sessionId ? `${API_BASE}/api/v1/feedback/?session_id=${sessionId}` : `${API_BASE}/api/v1/feedback/`; const response = await fetch(url); if (!response.ok) { throw new Error('Failed to get feedback'); } return response.json(); } // Get feedback statistics export async function getFeedbackStats(sessionId?: string): Promise { const url = sessionId ? `${API_BASE}/api/v1/feedback/statistics?session_id=${sessionId}` : `${API_BASE}/api/v1/feedback/statistics`; const response = await fetch(url); if (!response.ok) { throw new Error('Failed to get statistics'); } return response.json(); } // Export feedback as CSV export async function exportFeedbackCSV(sessionId?: string): Promise { const url = sessionId ? `${API_BASE}/api/v1/feedback/export/csv?session_id=${sessionId}` : `${API_BASE}/api/v1/feedback/export/csv`; const response = await fetch(url); if (!response.ok) { throw new Error('No feedback data to export'); } const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = `fetalclip_feedback_${new Date().toISOString().slice(0, 10)}.csv`; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(downloadUrl); } // Delete feedback entry export async function deleteFeedback(feedbackId: string): Promise { const response = await fetch(`${API_BASE}/api/v1/feedback/${feedbackId}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('Failed to delete feedback'); } } // List of all fetal view labels (must match keys in prompt_fetal_view.json) export const FETAL_VIEW_LABELS = [ "abdomen", "brain", "femur", "heart", "kidney", "lips_nose", "profile_patient", "spine", "cervix", "cord", "diaphragm", "feet", "orbit" ];