feat: improve optical report with embedded Plotly and 4x PNG export
- Embed Plotly.js inline for offline viewing (fixes CDN loading issues) - Add 4x resolution PNG export for all charts via toImageButtonOptions - Add SAT3_Trajectory_V7 study (TPE warm-start from V5, 86 trials, WS=277.37) - Include V7 optimization report and configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,24 @@ _BLUE_PALETTE = ['#2563eb', '#3b82f6', '#60a5fa', '#93c5fd', '#1d4ed8', '#1e40af
|
||||
_MODE_COLORS = ['#2563eb', '#dc2626', '#16a34a', '#9333ea', '#ea580c', '#0891b2', '#4f46e5']
|
||||
_MODE_DASHES = ['solid', 'dash', 'dot', 'dashdot', 'longdash', 'longdashdot', 'solid']
|
||||
|
||||
# High-resolution PNG export settings
|
||||
PNG_EXPORT_SCALE = 4 # 4x resolution (e.g., 700px width -> 2800px export)
|
||||
PNG_EXPORT_FORMAT = 'png'
|
||||
|
||||
def get_plotly_config(filename_prefix="plot"):
|
||||
"""Get Plotly config with high-resolution PNG export settings."""
|
||||
return {
|
||||
'toImageButtonOptions': {
|
||||
'format': PNG_EXPORT_FORMAT,
|
||||
'filename': filename_prefix,
|
||||
'height': None, # Use current height
|
||||
'width': None, # Use current width
|
||||
'scale': PNG_EXPORT_SCALE, # 4x resolution multiplier
|
||||
},
|
||||
'displaylogo': False,
|
||||
'modeBarButtonsToAdd': ['hoverClosest3d'],
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Data Extraction Helpers
|
||||
@@ -483,7 +501,7 @@ def _metric_color(value, target):
|
||||
return 'color: #dc2626; font-weight: 700;' # red
|
||||
|
||||
|
||||
def make_surface_plot(X, Y, W_res, mask, inner_radius=None, title="", amp=0.5, downsample=PLOT_DOWNSAMPLE):
|
||||
def make_surface_plot(X, Y, W_res, mask, inner_radius=None, title="", amp=0.5, downsample=PLOT_DOWNSAMPLE, include_plotlyjs=False):
|
||||
"""Create a 3D surface plot of residual WFE."""
|
||||
Xm, Ym, Wm = X[mask], Y[mask], W_res[mask]
|
||||
|
||||
@@ -578,7 +596,9 @@ def make_surface_plot(X, Y, W_res, mask, inner_radius=None, title="", amp=0.5, d
|
||||
height=650,
|
||||
width=1200,
|
||||
)
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id=f"surface_{title.replace(' ','_')}")
|
||||
div_id = f"surface_{title.replace(' ','_')}"
|
||||
config = get_plotly_config(f"WFE_Surface_{title.replace(' ','_')}")
|
||||
return fig.to_html(include_plotlyjs=include_plotlyjs, full_html=False, div_id=div_id, config=config)
|
||||
|
||||
|
||||
def make_bar_chart(coeffs, title="Zernike Coefficients", max_modes=50):
|
||||
@@ -606,7 +626,9 @@ def make_bar_chart(coeffs, title="Zernike Coefficients", max_modes=50):
|
||||
tickfont=dict(color='#475569')),
|
||||
yaxis=dict(autorange='reversed', tickfont=dict(size=10, color='#1e293b')),
|
||||
)
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id=f"bar_{title.replace(' ','_')}")
|
||||
div_id = f"bar_{title.replace(' ','_')}"
|
||||
config = get_plotly_config(f"Zernike_Coefficients_{title.replace(' ','_')}")
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id=div_id, config=config)
|
||||
|
||||
|
||||
def make_trajectory_plot(angles, coefficients_relative, mode_groups, sensitivity, title=""):
|
||||
@@ -693,7 +715,8 @@ def make_trajectory_plot(angles, coefficients_relative, mode_groups, sensitivity
|
||||
bordercolor='#e2e8f0', borderwidth=1,
|
||||
font=dict(size=10, color='#1e293b')),
|
||||
)
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="trajectory_plot")
|
||||
config = get_plotly_config("Zernike_Trajectory")
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="trajectory_plot", config=config)
|
||||
|
||||
|
||||
def make_sensitivity_bar(sensitivity_dict):
|
||||
@@ -732,7 +755,8 @@ def make_sensitivity_bar(sensitivity_dict):
|
||||
bordercolor='#e2e8f0', borderwidth=1,
|
||||
font=dict(color='#1e293b')),
|
||||
)
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="sensitivity_bar")
|
||||
config = get_plotly_config("Sensitivity_Axial_vs_Lateral")
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="sensitivity_bar", config=config)
|
||||
|
||||
|
||||
def make_per_angle_rms_plot(angle_rms_data, ref_angle=20):
|
||||
@@ -759,7 +783,8 @@ def make_per_angle_rms_plot(angle_rms_data, ref_angle=20):
|
||||
tickfont=dict(color='#475569')),
|
||||
xaxis=dict(tickfont=dict(color='#1e293b', size=12)),
|
||||
)
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="per_angle_rms")
|
||||
config = get_plotly_config("Per_Angle_RMS_WFE")
|
||||
return fig.to_html(include_plotlyjs=False, full_html=False, div_id="per_angle_rms", config=config)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -943,10 +968,11 @@ def generate_report(
|
||||
print("\nGenerating HTML report...")
|
||||
|
||||
# Surface plots
|
||||
# First plot embeds the full Plotly library (~3.5MB) for offline viewing
|
||||
surf_40 = make_surface_plot(
|
||||
angle_results[40]['X_rel'], angle_results[40]['Y_rel'],
|
||||
angle_results[40]['rms_rel']['W_res_filt'], angle_results[40]['rms_rel']['mask'],
|
||||
inner_radius=inner_radius, title="40 vs 20"
|
||||
inner_radius=inner_radius, title="40 vs 20", include_plotlyjs=True
|
||||
)
|
||||
surf_60 = make_surface_plot(
|
||||
angle_results[60]['X_rel'], angle_results[60]['Y_rel'],
|
||||
@@ -1123,7 +1149,7 @@ def generate_report(
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{title}</title>
|
||||
<script src="https://cdn.plot.ly/plotly-3.3.1.min.js"></script>
|
||||
<!-- Plotly.js is embedded inline in the first surface plot for offline viewing -->
|
||||
<style>
|
||||
:root {{
|
||||
--bg-primary: #ffffff;
|
||||
|
||||
Reference in New Issue
Block a user