import { useState, useRef, useCallback, useEffect } from 'react' import './App.css' const API = 'http://localhost:8000' const MODELS = ['Baseline', 'GAN', 'EBM'] const MODEL_META = { GAN: { color: '#4f8ef7', desc: 'ConvTranspose2d Generator + Spectral Norm Discriminator' }, EBM: { color: '#a855f7', desc: 'Energy-Based Model via Langevin Dynamics (MCMC sampling)' }, } // ── Upload Zone ─────────────────────────────────────────────────────────────── function UploadZone({ onUpload, preview }) { const [dragging, setDragging] = useState(false) const inputRef = useRef() const handle = useCallback((file) => { if (file && file.type.startsWith('image/')) onUpload(file) }, [onUpload]) return (
{ e.preventDefault(); setDragging(true) }} onDragLeave={() => setDragging(false)} onDrop={e => { e.preventDefault(); setDragging(false); handle(e.dataTransfer.files[0]) }} onClick={() => inputRef.current.click()} > handle(e.target.files[0])} /> {preview ? (
Uploaded X-ray Click to change
) : ( <>
🫁

Drop your chest X-ray here

or click to browse — PNG, JPG, JPEG

)}
) } // ── Augmentation Grid ───────────────────────────────────────────────────────── function AugGrid({ items, loading }) { if (loading) return
if (!items.length) return

Upload an image to see augmented versions.

return (
{items.map((item, i) => (
{item.label} {item.label}
))}
) } // ── Model Sample Column ─────────────────────────────────────────────────────── function ModelColumn({ name, images }) { const meta = MODEL_META[name] return (
{name} {meta.desc}
{images.length === 0 ? [0].map(i =>
) : images.map((img, i) => (
{`${name} Sample
)) }
) } // ── Confidence Bar ──────────────────────────────────────────────────────────── function ConfidenceBar({ label, value, color }) { return (
{label}
{(value * 100).toFixed(1)}%
) } // ── Dashboard Component ─────────────────────────────────────────────────────── function Dashboard({ navigate }) { const [metrics, setMetrics] = useState(null) const [samples, setSamples] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { Promise.all([ fetch(`${API}/metrics_images`).then(r => r.json()), fetch(`${API}/dataset_samples`).then(r => r.json()) ]) .then(([metricsData, samplesData]) => { setMetrics(metricsData) setSamples(samplesData) setLoading(false) }) .catch(() => setLoading(false)) }, []) return (
navigate('/', e)} className="nav-link" style={{ marginBottom: '16px', display: 'inline-block' }}> ← Back to Generator

Model Evaluation Metrics

Confusion Matrices and ROC Curves for Baseline, GAN, and EBM

{loading ? (

Loading metrics and dataset samples...

) : !metrics ? (

Failed to load metrics. Check backend.

) : (
{/* Dataset Samples Section */} {samples && (

PneumoniaMNIST Dataset Samples

Real examples from the test set used to evaluate the models.

Normal (Healthy) The minority class in training (Needs augmentation)
{samples.normal.map((img, i) => ( Normal ))}
Pneumonia The majority class (Notice the cloudy opacities)
{samples.pneumonia.map((img, i) => ( Pneumonia ))}
)} {/* Explanation Section */}

Why did EBM outperform GAN?

1. No Mode Collapse: GANs often suffer from mode collapse, where the generator finds a few realistic examples and repeats them. This creates sharp but repetitive images, failing to capture the full natural diversity of human lungs.

2. Diverse Distribution Modeling: EBMs (via Langevin dynamics) explore the entire continuous space of the data distribution. Because they assign low "energy" to all realistic lung states, EBMs generate a massive, diverse variety of healthy lungs including subtle variations in rib cage shapes and tissue densities.

Conclusion: When the EBM-generated "Normal" images were added to the dataset, they provided the classifier with a much wider, richer spectrum of healthy lungs to learn from. This led to a more robust decision boundary, drastically reducing False Positives from 10 (Baseline) to 5 (EBM), pushing Specificity to 96.30%.

{/* Metrics Section */}

Model Metrics

{['Baseline', 'GAN', 'EBM'].map(model => (

{model} Metrics

Confusion Matrix

{metrics[model]?.cm ? ( {`${model} ) :
}

ROC Curve

{metrics[model]?.roc ? ( {`${model} ) :
}
))}
)}
) } // ── Main App ────────────────────────────────────────────────────────────────── export default function App() { const [currentRoute, setCurrentRoute] = useState(window.location.pathname) const [file, setFile] = useState(null) const [preview, setPreview] = useState('') const [augments, setAugments] = useState([]) const [genData, setGenData] = useState({}) const [result, setResult] = useState(null) const [model, setModel] = useState('Baseline') const [loadingAug, setLoadingAug] = useState(false) const [loadingGen, setLoadingGen] = useState(false) const [loadingClf, setLoadingClf] = useState(false) useEffect(() => { const handleLocationChange = () => setCurrentRoute(window.location.pathname) window.addEventListener('popstate', handleLocationChange) return () => window.removeEventListener('popstate', handleLocationChange) }, []) const navigate = (path, e) => { if (e) e.preventDefault() window.history.pushState({}, '', path) setCurrentRoute(path) } const handleUpload = async (f) => { setFile(f) setPreview(URL.createObjectURL(f)) setAugments([]); setGenData({}); setResult(null) setLoadingAug(true) setLoadingGen(true) try { const fd = new FormData(); fd.append('file', f) const [augRes, genRes] = await Promise.all([ fetch(`${API}/augment`, { method: 'POST', body: fd }), fetch(`${API}/generate?n=1`, { method: 'POST' }) ]) const [augData, genData] = await Promise.all([augRes.json(), genRes.json()]) setAugments(augData.augmentations) setGenData(genData) } catch { alert('Backend not reachable. Make sure FastAPI is running on port 8000.') } finally { setLoadingAug(false); setLoadingGen(false) } } const handleClassify = async () => { if (!file) return setLoadingClf(true); setResult(null) try { const fd = new FormData(); fd.append('file', file); fd.append('model_name', model) const res = await fetch(`${API}/classify`, { method: 'POST', body: fd }) setResult(await res.json()) } catch { alert('Classification failed. Check backend.') } finally { setLoadingClf(false) } } if (currentRoute === '/dashboard') { return } return (
CVPR 2026 — BERR 4743

Generative Medical Imaging
Augmentation Dashboard

PneumoniaMNIST • GAN • EBM

Upload Chest X-ray

Step 1
{/* Traditional Augmentations */}

Traditional Augmentations

Row 1

Standard image transforms applied to the uploaded X-ray — flips, rotations, brightness, blur, and cropping.

{/* Generative Samples — GAN & EBM */}

Generative Model Samples

Row 2 — GAN & EBM

Synthetic Normal lung images auto-generated when you upload — these are what each model added to the training set to fix class imbalance.

{loadingGen && (

Generating GAN & EBM samples…

)} {!loadingGen && Object.keys(genData).length === 0 && (

Upload an image above to auto-generate GAN & EBM samples.

)} {Object.keys(genData).length > 0 && (
)}
{/* Classification */}

Classification

Step 2 — Run Inference

Select a ResNet50 model trained with each augmentation strategy and classify your uploaded X-ray.

{loadingClf &&
} {result && (
{result.prediction === 'Normal' ? '✅' : '⚠️'}
Prediction
{result.prediction}
{result.model}
)}
{/* Results Summary Table */}

Model Performance Summary

PneumoniaMNIST Test Set
{[ { name: 'Baseline', acc: 97.90, sens: 99.74, spec: 92.59, auc: 0.9983, fp: 10, fn: 1 }, { name: 'GAN', acc: 98.47, sens: 99.74, spec: 94.81, auc: 0.9979, fp: 7, fn: 1 }, { name: 'EBM', acc: 98.85, sens: 99.74, spec: 96.30, auc: 0.9993, fp: 5, fn: 1, best: true }, ].map(r => ( ))}
ModelAccuracySensitivitySpecificityAUCFP ↓FN ↓
{r.name !== 'Baseline' && ( )} {r.name} {r.best && 🏆 Best} {r.acc.toFixed(2)}% {r.sens.toFixed(2)}% {r.spec.toFixed(2)}% {r.auc.toFixed(4)} = 10 ? 'cell-worst' : ''}>{r.fp} = 4 ? 'cell-worst' : ''}>{r.fn}
) }