embedologist's picture
feat(tflite): integrate medgemma_micro_cardio_350m model and M2 testing dashboard
63139fb
Raw History Blame Contribute Delete
19.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MedGemma-Micro | Mobile Cardiology Edge AI (iOS & Android)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<!-- App Container -->
<div class="app-layout">
<!-- Top Navigation Header -->
<header class="top-nav">
<div class="nav-brand">
<div class="logo-pulse">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
</svg>
</div>
<div class="brand-text">
<span class="brand-title">MedGemma-Micro</span>
<span class="brand-sub">MacBook M2 · LiteRT & iOS Core ML</span>
</div>
</div>
<!-- Model Engine Switcher & M2 Benchmark Action -->
<div class="model-control-bar">
<div class="engine-selector-box">
<label for="model-select" class="selector-label">
<span class="pulse-indicator pulse-normal" id="engine-status-dot"></span>
<span class="label-text">TESTING MODEL:</span>
</label>
<div class="custom-select-wrapper">
<select id="model-select" class="model-dropdown">
<option value="tflite_350m" selected>⚡ medgemma_micro_cardio_350m.tflite (301.9 MB · M2 LiteRT)</option>
<option value="pytorch_edge">🧠 medgemma_micro_cardio_edge.safetensors (336.3 MB · PyTorch)</option>
</select>
</div>
</div>
<button class="btn btn-m2-benchmark" id="btn-open-benchmark" title="Execute 4-Stage MacBook M2 Validation Suite">
<span class="btn-icon">🚀</span>
<span>Run M2 Benchmark</span>
</button>
</div>
<!-- Telemetry HUD Badges -->
<div class="telemetry-hud">
<div class="hud-pill" id="hud-accel-pill">
<span class="hud-label">CHIP ACCEL</span>
<span class="hud-val text-cyan" id="hud-hardware">MacBook M2 · XNNPACK</span>
</div>
<div class="hud-pill">
<span class="hud-dot online"></span>
<span class="hud-label">MODEL SIZE</span>
<span class="hud-val" id="hud-size">301.93 MB</span>
</div>
<div class="hud-pill">
<span class="hud-label">BUDGET</span>
<span class="hud-val highlight" id="hud-budget">&lt; 350 MB</span>
<span class="hud-sub" id="hud-headroom">(48.1 MB Headroom)</span>
</div>
<div class="hud-pill">
<span class="hud-label">SIGNATURE</span>
<span class="hud-val text-cyan" id="hud-sig">Dual-Signature</span>
</div>
</div>
</header>
<!-- Main Grid Dashboard -->
<main class="dashboard-grid">
<!-- Left Column: Sensor Monitor & Arrhythmia Classifier -->
<section class="panel sensor-panel">
<!-- Panel Header -->
<div class="panel-header">
<div class="panel-title-group">
<h2 class="panel-title">PPG Waveform Monitor</h2>
<span class="panel-caption">90s Continuous Pulse Stream @ 25 Hz (2250 Samples)</span>
</div>
<div class="rhythm-status-badge" id="current-rhythm-badge">
<span class="pulse-indicator pulse-normal" id="status-pulse-dot"></span>
<span id="badge-rhythm-name">Normal Sinus Rhythm</span>
</div>
</div>
<!-- Oscilloscope Waveform Display -->
<div class="oscilloscope-container">
<div class="scope-grid-overlay"></div>
<canvas id="ppg-canvas" width="800" height="240"></canvas>
<div class="scope-hud">
<span class="scope-time">T: 0.0s - 90.0s</span>
<span class="scope-gain">Gain: 1.0x (Calibrated)</span>
<span class="scope-fps" id="canvas-fps">60 FPS</span>
</div>
</div>
<!-- Waveform Controls & Preset Selector -->
<div class="scope-controls">
<div class="control-row">
<span class="control-label">Simulate Cardiac Condition:</span>
<div class="condition-chips" id="condition-chips">
<button class="chip active" data-condition="0">
<span class="chip-dot normal"></span> Normal Sinus
</button>
<button class="chip" data-condition="1">
<span class="chip-dot afib"></span> AFib (Arrhythmia)
</button>
<button class="chip" data-condition="2">
<span class="chip-dot brady"></span> Bradycardia
</button>
<button class="chip" data-condition="3">
<span class="chip-dot tachy"></span> Tachycardia
</button>
<button class="chip" data-condition="4">
<span class="chip-dot pvc"></span> PVC Ectopic
</button>
</div>
</div>
<div class="actions-row">
<button class="btn btn-secondary btn-sm" id="btn-toggle-sweep">
<span id="sweep-icon">⏸</span> <span id="sweep-text">Pause Monitor</span>
</button>
<button class="btn btn-secondary btn-sm" id="btn-regen-ppg">
<span>↻</span> Regenerate Signal
</button>
<label class="toggle-switch-label">
<input type="checkbox" id="toggle-noise" checked>
<span class="switch-slider"></span>
<span class="switch-text">Mobile PPG Motion Noise</span>
</label>
</div>
</div>
<!-- Wear OS Smartwatch (Samsung Galaxy Watch 4) Ingestion Bench -->
<div class="wearos-bench-card">
<div class="wearos-bench-header">
<div class="wearos-title-group">
<span class="wearos-icon">⌚</span>
<div>
<span class="wearos-title">Wear OS Telemetry Ingestion (Galaxy Watch 4/5/6)</span>
<span class="wearos-sub">Raw BioActive ADC (650k counts) &bull; ChannelClient Ingestion &bull; Anti-Aliased Resampling</span>
</div>
</div>
<div class="wearos-sqi-badge" id="wearos-sqi-badge">
<span class="sqi-dot" id="wearos-sqi-dot"></span>
<span id="wearos-sqi-text">Buffer Active</span>
</div>
</div>
<div class="wearos-buffer-bar-wrapper">
<div class="wearos-buffer-labels">
<span>90s Rolling Ring Buffer: <strong id="wearos-buffer-count">2,250 / 2,250 samples</strong></span>
<span id="wearos-buffer-pct" class="text-cyan font-mono">100% Full</span>
</div>
<div class="wearos-progress-track">
<div class="wearos-progress-fill" id="wearos-progress-fill" style="width: 100%;"></div>
</div>
</div>
<div class="wearos-actions-row">
<span class="wearos-row-label">Stream Galaxy Watch 4 Scenario:</span>
<div class="wearos-chips">
<button class="wearos-btn" id="btn-stream-w4-normal" data-cond="0" data-fs="25">
<span>⌚</span> Watch 4 Normal (25 Hz)
</button>
<button class="wearos-btn" id="btn-stream-w4-afib" data-cond="1" data-fs="25">
<span>⚡</span> Watch 4 AFib (25 Hz)
</button>
<button class="wearos-btn" id="btn-stream-w4-100hz" data-cond="3" data-fs="100">
<span>🔄</span> Watch 4 Tachy (100 Hz Decimated)
</button>
<button class="wearos-btn" id="btn-stream-w4-detached" data-cond="5" data-fs="25">
<span>🚫</span> Off-Wrist Rejection
</button>
</div>
</div>
</div>
<!-- Extracted HRV & Physiological Telemetry -->
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">HEART RATE</span>
<span class="metric-unit">BPM</span>
</div>
<div class="metric-value" id="metric-hr">72.0</div>
<div class="metric-sub" id="metric-hr-sub">Resting Rhythm</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">rMSSD (HRV)</span>
<span class="metric-unit">ms</span>
</div>
<div class="metric-value" id="metric-rmssd">38.4</div>
<div class="metric-sub">Parasympathetic Vagal Tone</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">SDNN</span>
<span class="metric-unit">ms</span>
</div>
<div class="metric-value" id="metric-sdnn">41.2</div>
<div class="metric-sub">RR Regularity Index</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">SENSOR LATENCY</span>
<span class="metric-unit">ms</span>
</div>
<div class="metric-value text-emerald" id="metric-latency">14.8</div>
<div class="metric-sub">1D-Conformer ANE / NPU Pass</div>
</div>
</div>
<!-- Arrhythmia Classifier Section -->
<div class="classification-section">
<div class="class-header">
<div class="class-title-group">
<h3 class="section-heading">Multi-Task Arrhythmia Classifier</h3>
<span class="section-sub" id="classifier-engine-sub">1D-Conformer Biosignal Encoder (medgemma_micro_cardio_350m.tflite)</span>
</div>
<button class="btn btn-primary btn-sm" id="btn-run-classifier">
<span>⚡</span> Run Edge Classification
</button>
</div>
<!-- Class Confidence Bars -->
<div class="probability-bars" id="prob-bars-container">
<!-- Dynamically injected via app.js -->
</div>
</div>
</section>
<!-- Right Column: Multimodal Clinical Chat & Triage Assistant -->
<section class="panel chat-panel">
<div class="chat-header">
<div class="chat-title-group">
<h2 class="panel-title">Cardiology Clinical Assistant</h2>
<span class="panel-caption" id="chat-engine-caption">medgemma_micro_cardio_350m.tflite (11-Layer Transformer · M2 LiteRT)</span>
</div>
<!-- Multimodal Soft Prefix Switch & History Clear -->
<div class="multimodal-switch-container">
<button class="btn btn-outline btn-sm" id="btn-clear-chat" title="Clear conversation history">
<span>🧹</span> Clear Chat
</button>
<label class="toggle-switch-label" title="Condition LM directly on 90s PPG sensor soft prompt embeddings">
<input type="checkbox" id="toggle-multimodal">
<span class="switch-slider"></span>
<span class="switch-text font-semibold">PPG Multimodal Telemetry</span>
</label>
<div class="bridge-tag active" id="bridge-indicator">
<span class="bridge-dot"></span>
<span id="bridge-text">TFLite 768-D Semantic Engine Active</span>
</div>
</div>
</div>
<!-- Preset Clinical Test Cases -->
<div class="presets-drawer">
<span class="presets-caption">Clinical Presets:</span>
<div class="presets-scroll" id="presets-container">
<!-- Dynamically loaded -->
</div>
</div>
<!-- Chat Conversation Messages -->
<div class="chat-messages" id="chat-messages">
<!-- Initial Welcome Message -->
<div class="message-bubble assistant-msg">
<div class="msg-avatar">
<span>🩺</span>
</div>
<div class="msg-body">
<div class="msg-author">
<span class="name">MedGemma-Micro</span>
<span class="tag" id="welcome-engine-tag">350M LiteRT M2</span>
</div>
<div class="msg-content">
<p>Hello! I am <strong>MedGemma-Micro</strong> running directly on your <strong>MacBook M2</strong> via <strong>TensorFlow Lite (LiteRT with XNNPACK CPU acceleration)</strong>.</p>
<p>You are actively testing the unified multi-signature model <code>medgemma_micro_cardio_350m.tflite</code> (<strong>301.93 MB</strong>, strict sub-350MB budget). You can:</p>
<ul>
<li><strong>Classify Arrhythmias:</strong> Test 90s PPG pulse streams across <em>Normal Sinus, AFib, Bradycardia, Tachycardia, and PVC</em>.</li>
<li><strong>Wear OS Smartwatch Bench:</strong> Ingest streaming Galaxy Watch 4/5/6 PPG data, test 100 Hz decimation, and off-wrist lead-off rejection.</li>
<li><strong>Cardiology Q&amp;A:</strong> Inquire about medications, DASH diet, exercise guidelines, sleep apnea, or emergency triage (matched across 1,552 indexed clinical guidelines with 768-D semantic embeddings).</li>
<li><strong>Run Automated M2 Benchmark:</strong> Click the <strong class="text-cyan">"🚀 Run M2 Benchmark"</strong> button in the top navigation bar to execute the full 4-stage validation suite.</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Chat Input & Toolbar -->
<div class="chat-input-area">
<form id="chat-form" class="chat-form">
<textarea
id="user-input"
rows="2"
placeholder="Ask a cardiology question or evaluate the active PPG waveform (e.g., 'What medications are indicated for this rhythm?')..."
></textarea>
<div class="chat-form-actions">
<div class="telemetry-tag" id="chat-telemetry">
<span id="chat-tps">Ready</span>
</div>
<button type="submit" class="btn btn-primary btn-send" id="btn-send">
<span>Send</span>
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
</svg>
</button>
</div>
</form>
<div class="chat-disclaimer">
<span>⚠️ <strong>Medical Disclaimer:</strong> For educational purposes only, not a prescription or treatment plan. <strong>Do not start, stop, or change any medication without your doctor’s approval.</strong></span>
</div>
</div>
</section>
</main>
</div>
<!-- M2 TFLite Benchmark Modal -->
<div class="modal-backdrop" id="benchmark-modal" style="display: none;">
<div class="modal-dialog">
<div class="modal-header">
<div class="modal-title-group">
<span class="modal-icon">⚡</span>
<div>
<h3 class="modal-title">MacBook M2 TFLite Automated Benchmark Suite</h3>
<span class="modal-sub">Validation of <code>medgemma_micro_cardio_350m.tflite</code> (301.93 MB)</span>
</div>
</div>
<button class="btn-close-modal" id="btn-close-benchmark" aria-label="Close modal">&times;</button>
</div>
<div class="modal-body">
<div class="benchmark-summary-banner" id="benchmark-banner">
<div class="banner-status-badge" id="banner-overall-status">Ready to Benchmark</div>
<div class="banner-description" id="banner-desc">
Executes 4 verification suites: Model Size &amp; Mobile Budget Audit, 50-window Arrhythmia Stability across 10 heart rates (0% flapping), 25 Clinical Cardiology QA cases, and Apple Silicon M2 Latency.
</div>
<button class="btn btn-primary btn-lg" id="btn-start-benchmark">
<span>▶</span> Start Automated M2 Validation
</button>
</div>
<div class="benchmark-grid" id="benchmark-results-grid" style="display: none;">
<!-- Card 1: Size & Budget -->
<div class="bench-card" id="card-size">
<div class="bench-card-header">
<span class="card-icon">📁</span>
<span class="card-title">Model File Size Budget</span>
<span class="card-badge" id="badge-size-status">Checking</span>
</div>
<div class="bench-metric" id="val-model-size">301.93 MB</div>
<div class="bench-sub">Target: 300 MB - 350 MB (&lt; 512 MB Budget Limit)</div>
</div>
<!-- Card 2: Stability -->
<div class="bench-card" id="card-stability">
<div class="bench-card-header">
<span class="card-icon">💓</span>
<span class="card-title">Arrhythmia Stability</span>
<span class="card-badge" id="badge-stability-status">Testing</span>
</div>
<div class="bench-metric" id="val-stability-score">100.0%</div>
<div class="bench-sub">50 consecutive 90s windows across 10 heart rates (45-115 BPM)</div>
</div>
<!-- Card 3: Q&A Accuracy -->
<div class="bench-card" id="card-qa">
<div class="bench-card-header">
<span class="card-icon">🩺</span>
<span class="card-title">Cardiology Q&amp;A Accuracy</span>
<span class="card-badge" id="badge-qa-status">Testing</span>
</div>
<div class="bench-metric" id="val-qa-score">100.0%</div>
<div class="bench-sub">25 verified clinical cardiology cases across guidelines</div>
</div>
<!-- Card 4: M2 Latency -->
<div class="bench-card" id="card-latency">
<div class="bench-card-header">
<span class="card-icon">⚡</span>
<span class="card-title">M2 Execution Latency</span>
<span class="card-badge" id="badge-latency-status">Benchmarked</span>
</div>
<div class="bench-metric" id="val-bench-latency">42 ms</div>
<div class="bench-sub">XNNPACK CPU Delegate on Apple Silicon M2</div>
</div>
</div>
<!-- Detailed Telemetry Tabs -->
<div class="benchmark-details-section" id="bench-details" style="display: none;">
<h4 class="details-title">Verification Breakdown</h4>
<div class="details-tabs">
<button class="details-tab active" id="tab-btn-stability" data-tab="tab-stability">Arrhythmia Windows (50/50)</button>
<button class="details-tab" id="tab-btn-qa" data-tab="tab-qa">25 Clinical QA Cases</button>
</div>
<div class="details-content" id="tab-stability-content">
<div class="stability-table-wrap" id="stability-table-container"></div>
</div>
<div class="details-content" id="tab-qa-content" style="display: none;">
<div class="qa-table-wrap" id="qa-table-container"></div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>