/** * Zernike Viewer Component * Displays interactive Zernike wavefront analysis for mirror optimization trials * * Features: * - 3D surface residual plots (Plotly) * - RMS metrics tables * - Zernike coefficient bar charts * - Tab navigation for different angle comparisons (40°, 60°, 90° vs 20°) */ import { useState, useEffect } from 'react'; import { X, RefreshCw, Activity, ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react'; interface ZernikeComparison { html: string; rms_global: number; rms_filtered: number; title: string; } interface ZernikeData { study_id: string; trial_number: number; comparisons: Record; available_comparisons: string[]; } interface ZernikeViewerProps { studyId: string; trialNumber: number; onClose: () => void; } export function ZernikeViewer({ studyId, trialNumber, onClose }: ZernikeViewerProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedTab, setSelectedTab] = useState('40_vs_20'); const fetchZernikeData = async () => { setLoading(true); setError(null); try { const response = await fetch( `/api/optimization/studies/${studyId}/trials/${trialNumber}/zernike` ); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } const result = await response.json(); setData(result); // Select first available tab if (result.available_comparisons?.length > 0) { setSelectedTab(result.available_comparisons[0]); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load Zernike analysis'); } finally { setLoading(false); } }; useEffect(() => { fetchZernikeData(); }, [studyId, trialNumber]); // Tab labels for display const tabLabels: Record = { '40_vs_20': '40° vs 20°', '60_vs_20': '60° vs 20°', '90_vs_20': '90° vs 20° (Mfg)', }; // Get current comparison data const currentComparison = data?.comparisons[selectedTab]; // Navigate between tabs const navigateTab = (direction: 'prev' | 'next') => { if (!data?.available_comparisons) return; const currentIndex = data.available_comparisons.indexOf(selectedTab); if (direction === 'prev' && currentIndex > 0) { setSelectedTab(data.available_comparisons[currentIndex - 1]); } else if (direction === 'next' && currentIndex < data.available_comparisons.length - 1) { setSelectedTab(data.available_comparisons[currentIndex + 1]); } }; // Open in new window const openInNewWindow = () => { if (!currentComparison?.html) return; const newWindow = window.open('', '_blank'); if (newWindow) { newWindow.document.write(currentComparison.html); newWindow.document.close(); } }; return (
e.stopPropagation()} > {/* Header */}

Zernike Analysis - Trial #{trialNumber}

{studyId}

{/* RMS Quick Summary */} {currentComparison && (
Global RMS
{currentComparison.rms_global.toFixed(2)} nm
Filtered RMS
{currentComparison.rms_filtered.toFixed(2)} nm
)}
{/* Tabs */} {data?.available_comparisons && data.available_comparisons.length > 0 && (
{data.available_comparisons.map((tab) => ( ))}
)} {/* Content */}
{loading && (

Generating Zernike analysis...

This may take a few seconds for large meshes

)} {error && (

{error.includes('surrogate') || error.includes('NN') ? 'No FEA Results' : 'Analysis Failed'}

{error}

{error.includes('surrogate') || error.includes('NN') ? (

Try selecting a trial with "FEA" source tag instead of "NN"

) : ( )}
)} {!loading && !error && currentComparison && (