Spaces:
Running
Running
| 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<ClassificationResponse> { | |
| 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<GestationalAgeResponse> { | |
| 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<PreviewResponse> { | |
| 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<boolean> { | |
| 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<string, { total: number; correct: number; incorrect: number; not_sure: number }>; | |
| } | |
| 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<SessionInfo> { | |
| 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<SessionInfo> { | |
| 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<void> { | |
| await fetch(`${API_BASE}/api/v1/feedback/session/${sessionId}/image-analyzed`, { | |
| method: 'POST', | |
| }); | |
| } | |
| // Submit feedback | |
| export async function submitFeedback(feedback: FeedbackCreate): Promise<FeedbackEntry> { | |
| 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<FeedbackEntry[]> { | |
| 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<FeedbackStats> { | |
| 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<void> { | |
| 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<void> { | |
| 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" | |
| ]; | |