Numan Saeed
Fix GA biometry display for abdomen and femur views
0f97254
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"
];