gratinglab 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gratinglab-0.2.4/src/gratinglab.egg-info → gratinglab-0.2.6}/PKG-INFO +1 -1
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/__init__.py +1 -1
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/theming.py +10 -1
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/io/boundary.py +4 -2
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/boundary/__init__.py +4 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/boundary/average.py +116 -16
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/boundary/pipeline.py +60 -12
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/boundary_view.py +7 -1
- gratinglab-0.2.6/src/gratinglab/metrology/gui/qt/layout.py +95 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/io/metrics.py +10 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6/src/gratinglab.egg-info}/PKG-INFO +1 -1
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab.egg-info/SOURCES.txt +1 -0
- gratinglab-0.2.6/tests/test_four_way.py +217 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_docs.py +7 -2
- gratinglab-0.2.4/src/gratinglab/metrology/gui/qt/layout.py +0 -46
- {gratinglab-0.2.4 → gratinglab-0.2.6}/LICENSE +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/README.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/pyproject.toml +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/setup.cfg +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/convergence.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/cmethod_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/mathtext.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/main_window.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/setup_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/qt/worker.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/rcwa_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/richtext.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/state.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/io/ggp.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/analyzer.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/problem.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/profiles.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/result.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/base.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab/solvers/scalar.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab.egg-info/requires.txt +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_changelog.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_checks.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_cmethod.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_compare.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_conditioning.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_convergence.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_cross_method.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_fresnel.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_geometry.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_ggp.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_cmethod_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_cmethod_tab.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_mathtext.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_qt.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_rcwa_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_richtext.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_state.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_illumination.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_core.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_materials.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_measured.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_perturbation.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_profiles.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_rcwa.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_resolution.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_result.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_scalar.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_solver_execution.py +0 -0
- {gratinglab-0.2.4 → gratinglab-0.2.6}/tests/test_solver_progress.py +0 -0
|
@@ -22,7 +22,7 @@ from .measured import MeasuredEfficiency, MeasurementProvenance
|
|
|
22
22
|
from .resolution import ResolvingPower, resolving_power
|
|
23
23
|
from .result import EfficiencyScan, OrderEfficiency, Provenance
|
|
24
24
|
|
|
25
|
-
__version__ = "0.2.
|
|
25
|
+
__version__ = "0.2.6"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -36,6 +36,7 @@ __all__ = [
|
|
|
36
36
|
"current",
|
|
37
37
|
"install",
|
|
38
38
|
"manager",
|
|
39
|
+
"settings",
|
|
39
40
|
"subscribe",
|
|
40
41
|
"swatch",
|
|
41
42
|
]
|
|
@@ -47,13 +48,21 @@ _KEY = "appearance/theme"
|
|
|
47
48
|
_PROPERTY = "_gratinglab_theme_manager"
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
+
def settings() -> QSettings:
|
|
52
|
+
"""The application's settings store, or the INI file ``GRATINGLAB_SETTINGS`` names.
|
|
53
|
+
|
|
54
|
+
Shared by everything the windows remember (the theme, the AFM column
|
|
55
|
+
width), so the test suite's redirect covers all of it at once.
|
|
56
|
+
"""
|
|
51
57
|
path = os.environ.get(SETTINGS_ENV)
|
|
52
58
|
if path:
|
|
53
59
|
return QSettings(path, QSettings.Format.IniFormat)
|
|
54
60
|
return QSettings("gratinglab", "gratinglab")
|
|
55
61
|
|
|
56
62
|
|
|
63
|
+
_settings = settings
|
|
64
|
+
|
|
65
|
+
|
|
57
66
|
def palette(theme: Theme) -> QPalette:
|
|
58
67
|
"""A ``QPalette`` for ``theme``, with disabled text dimmed to its muted colour."""
|
|
59
68
|
role = QPalette.ColorRole
|
|
@@ -88,8 +88,10 @@ class BoundaryFile:
|
|
|
88
88
|
This is the same problem ``BoundaryProfile.to_problem`` builds in
|
|
89
89
|
process, which is the purpose of the format; see
|
|
90
90
|
``tests/test_io_boundary.py``.
|
|
91
|
-
Roughness is not
|
|
92
|
-
gives
|
|
91
|
+
Roughness is not applied, for the reason that method's docstring
|
|
92
|
+
gives. The measured groove residual travels in
|
|
93
|
+
``metrics['groove_residual_rms_nm']`` and is passed only on request,
|
|
94
|
+
as ``roughness=``.
|
|
93
95
|
"""
|
|
94
96
|
from ..problem import Problem
|
|
95
97
|
|
|
@@ -14,8 +14,10 @@ from .swaths import SwathStatistics, swath_statistics
|
|
|
14
14
|
from .average import (
|
|
15
15
|
flatten_endpoints,
|
|
16
16
|
average_grooves,
|
|
17
|
+
groove_residuals,
|
|
17
18
|
normalize_profile,
|
|
18
19
|
profile_metrics,
|
|
20
|
+
select_grooves,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
__all__ = [
|
|
@@ -25,6 +27,8 @@ __all__ = [
|
|
|
25
27
|
'swath_statistics',
|
|
26
28
|
'flatten_endpoints',
|
|
27
29
|
'average_grooves',
|
|
30
|
+
'groove_residuals',
|
|
28
31
|
'normalize_profile',
|
|
29
32
|
'profile_metrics',
|
|
33
|
+
'select_grooves',
|
|
30
34
|
]
|
|
@@ -7,7 +7,7 @@ profile front-end now comes from gratinglab.metrology.core instead of a private
|
|
|
7
7
|
copy.
|
|
8
8
|
"""
|
|
9
9
|
import numpy as np
|
|
10
|
-
from scipy.interpolate import interp1d
|
|
10
|
+
from scipy.interpolate import interp1d, make_interp_spline
|
|
11
11
|
from scipy.ndimage import uniform_filter1d
|
|
12
12
|
|
|
13
13
|
from ..core.processing import extract_single_groove
|
|
@@ -30,6 +30,36 @@ def flatten_endpoints(x, y):
|
|
|
30
30
|
return y - line
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def select_grooves(x, y, groove_centers, period_nm, margin=0.0,
|
|
34
|
+
min_half_width=10, require_full_period=True):
|
|
35
|
+
"""
|
|
36
|
+
The grooves `average_grooves` averages, each centred on its own axis in µm.
|
|
37
|
+
|
|
38
|
+
Separate so that `groove_residuals` measures exactly the set that went
|
|
39
|
+
into the average, chosen by the same rules rather than a second copy of
|
|
40
|
+
them. The parameters mean what they mean in `average_grooves`.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
a list of (x_g, y_g), possibly empty
|
|
44
|
+
"""
|
|
45
|
+
grooves = []
|
|
46
|
+
dx_nm = (x[1] - x[0]) * 1000
|
|
47
|
+
half_width = int(round((period_nm / 2) * (1 + margin) / dx_nm))
|
|
48
|
+
|
|
49
|
+
for center in groove_centers:
|
|
50
|
+
L = min(half_width, center, len(x) - 1 - center)
|
|
51
|
+
if require_full_period and L < half_width:
|
|
52
|
+
continue # the scan edge cuts into this groove's period
|
|
53
|
+
if L > min_half_width:
|
|
54
|
+
x_g, y_g = extract_single_groove(x, y, center, period_nm, margin,
|
|
55
|
+
allow_asymmetric=False)
|
|
56
|
+
# Re-create the exactly symmetric axis the original used, so that
|
|
57
|
+
# floating point matches to the last bit.
|
|
58
|
+
x_g = np.linspace(-L * dx_nm, L * dx_nm, len(y_g)) / 1000
|
|
59
|
+
grooves.append((x_g, y_g))
|
|
60
|
+
return grooves
|
|
61
|
+
|
|
62
|
+
|
|
33
63
|
def average_grooves(x, y, groove_centers, period_nm, margin=0.0, n_points=2000,
|
|
34
64
|
min_half_width=10, require_full_period=True):
|
|
35
65
|
"""
|
|
@@ -53,21 +83,9 @@ def average_grooves(x, y, groove_centers, period_nm, margin=0.0, n_points=2000,
|
|
|
53
83
|
Returns:
|
|
54
84
|
x_common, y_avg, y_std, n_used
|
|
55
85
|
"""
|
|
56
|
-
grooves =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for center in groove_centers:
|
|
61
|
-
L = min(half_width, center, len(x) - 1 - center)
|
|
62
|
-
if require_full_period and L < half_width:
|
|
63
|
-
continue # the scan edge cuts into this groove's period
|
|
64
|
-
if L > min_half_width:
|
|
65
|
-
x_g, y_g = extract_single_groove(x, y, center, period_nm, margin,
|
|
66
|
-
allow_asymmetric=False)
|
|
67
|
-
# Re-create the exactly symmetric axis the original used, so that
|
|
68
|
-
# floating point matches to the last bit.
|
|
69
|
-
x_g = np.linspace(-L * dx_nm, L * dx_nm, len(y_g)) / 1000
|
|
70
|
-
grooves.append((x_g, y_g))
|
|
86
|
+
grooves = select_grooves(x, y, groove_centers, period_nm, margin=margin,
|
|
87
|
+
min_half_width=min_half_width,
|
|
88
|
+
require_full_period=require_full_period)
|
|
71
89
|
|
|
72
90
|
if len(grooves) == 0:
|
|
73
91
|
raise ValueError(
|
|
@@ -90,6 +108,88 @@ def average_grooves(x, y, groove_centers, period_nm, margin=0.0, n_points=2000,
|
|
|
90
108
|
return x_common, np.mean(y_aligned, axis=0), np.std(y_aligned, axis=0), len(grooves)
|
|
91
109
|
|
|
92
110
|
|
|
111
|
+
def groove_residuals(grooves, x_common, y_avg, n_passes=3, n_steps=6):
|
|
112
|
+
"""
|
|
113
|
+
How far each groove departs from the average, once registered to it.
|
|
114
|
+
|
|
115
|
+
This is the RMS residual of each averaged groove about the average: the
|
|
116
|
+
part of the measured profile that does not repeat from one groove to the
|
|
117
|
+
next. It is reported, not applied. `BoundaryProfile.to_problem` and
|
|
118
|
+
docs/theory/metrology.md section 4 say why it is not a Nevot-Croce sigma.
|
|
119
|
+
|
|
120
|
+
A plain difference against `y_avg` does not measure that. Each groove is
|
|
121
|
+
centred on the sample nearest its minimum, so the set is misregistered by
|
|
122
|
+
up to half a pixel, and on a facet steeper than 45 degrees half a pixel of
|
|
123
|
+
shift is several nanometres of height. On the synthetic sawtooth with no
|
|
124
|
+
noise at all, the plain difference reads 2.1 nm. So each groove is first
|
|
125
|
+
registered to the average by a sub-pixel shift and a height offset, fitted
|
|
126
|
+
by Gauss-Newton on a cubic spline of its own samples, and the average is
|
|
127
|
+
recomputed from the registered grooves, `n_passes` times over. The shift is
|
|
128
|
+
bounded to one pixel either way, and the window is the aligned window the
|
|
129
|
+
average uses less one pixel at each end, so a shifted groove is never
|
|
130
|
+
extrapolated.
|
|
131
|
+
|
|
132
|
+
The residual is then taken at each groove's own samples rather than on the
|
|
133
|
+
dense common axis, because interpolating onto that axis smooths pixel-scale
|
|
134
|
+
structure: it read white noise 10 to 15% low. It is scaled by
|
|
135
|
+
sqrt(N / (N - 1)), since the average contains the groove it is compared
|
|
136
|
+
with, and is undefined for a single groove.
|
|
137
|
+
|
|
138
|
+
`y_avg` itself is not changed. It is still the unregistered mean, and is
|
|
139
|
+
what is exported.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
dict with
|
|
143
|
+
rms_nm: over every groove, or None for fewer than two grooves
|
|
144
|
+
per_groove_nm: one RMS per groove, in the order of `grooves`
|
|
145
|
+
shift_px: each groove's registration shift, in pixels
|
|
146
|
+
"""
|
|
147
|
+
n = len(grooves)
|
|
148
|
+
if n < 2:
|
|
149
|
+
return {'rms_nm': None, 'per_groove_nm': np.zeros(0),
|
|
150
|
+
'shift_px': np.zeros(0)}
|
|
151
|
+
|
|
152
|
+
dx = grooves[0][0][1] - grooves[0][0][0]
|
|
153
|
+
inner = (x_common > x_common[0] + dx) & (x_common < x_common[-1] - dx)
|
|
154
|
+
xi = x_common[inner]
|
|
155
|
+
reference = np.asarray(y_avg, dtype=float)[inner]
|
|
156
|
+
splines = [make_interp_spline(g[0], g[1], k=3) for g in grooves]
|
|
157
|
+
slopes = [spline.derivative() for spline in splines]
|
|
158
|
+
shifts = np.zeros(n)
|
|
159
|
+
|
|
160
|
+
for _ in range(n_passes):
|
|
161
|
+
registered = np.empty((n, len(xi)))
|
|
162
|
+
for k, (spline, slope) in enumerate(zip(splines, slopes)):
|
|
163
|
+
d = shifts[k]
|
|
164
|
+
for _ in range(n_steps):
|
|
165
|
+
r = spline(xi + d) - reference
|
|
166
|
+
# Least squares in (shift, offset). The offset's normal
|
|
167
|
+
# equation removes the means, which leaves one unknown.
|
|
168
|
+
g = slope(xi + d)
|
|
169
|
+
g = g - g.mean()
|
|
170
|
+
denom = np.dot(g, g)
|
|
171
|
+
if denom == 0:
|
|
172
|
+
break
|
|
173
|
+
d = float(np.clip(d - np.dot(g, r - r.mean()) / denom, -dx, dx))
|
|
174
|
+
shifts[k] = d
|
|
175
|
+
curve = spline(xi + d)
|
|
176
|
+
registered[k] = curve - np.mean(curve - reference)
|
|
177
|
+
reference = registered.mean(axis=0)
|
|
178
|
+
|
|
179
|
+
at_reference = make_interp_spline(xi, reference, k=3)
|
|
180
|
+
mean_square = np.empty(n)
|
|
181
|
+
for k, (x_g, y_g) in enumerate(grooves):
|
|
182
|
+
xs = x_g - shifts[k]
|
|
183
|
+
keep = (xs >= xi[0]) & (xs <= xi[-1])
|
|
184
|
+
r = y_g[keep] - at_reference(xs[keep])
|
|
185
|
+
mean_square[k] = np.mean((r - r.mean()) ** 2)
|
|
186
|
+
mean_square *= n / (n - 1)
|
|
187
|
+
|
|
188
|
+
return {'rms_nm': float(np.sqrt(np.mean(mean_square))),
|
|
189
|
+
'per_groove_nm': np.sqrt(mean_square),
|
|
190
|
+
'shift_px': shifts / dx}
|
|
191
|
+
|
|
192
|
+
|
|
93
193
|
def normalize_profile(x_avg, y_avg, period_nm, apply_smoothing=True,
|
|
94
194
|
smoothing_window=5):
|
|
95
195
|
"""
|
|
@@ -25,8 +25,8 @@ from ..core.region_mask import (GROOVE_REACH_PERIODS, bridge_nan,
|
|
|
25
25
|
describe_mask, groove_spacings_px, has_mask,
|
|
26
26
|
reject_grooves_near_nan,
|
|
27
27
|
tip_correct_around_nan)
|
|
28
|
-
from .average import (average_grooves, flatten_endpoints,
|
|
29
|
-
profile_metrics)
|
|
28
|
+
from .average import (average_grooves, flatten_endpoints, groove_residuals,
|
|
29
|
+
normalize_profile, profile_metrics, select_grooves)
|
|
30
30
|
|
|
31
31
|
__all__ = ["BoundaryProfile", "build_boundary_profile"]
|
|
32
32
|
|
|
@@ -66,6 +66,16 @@ class BoundaryProfile:
|
|
|
66
66
|
f"period {self.period_nm:.2f} nm, "
|
|
67
67
|
f"depth {self.metrics['groove_depth']:.4f} of period")
|
|
68
68
|
|
|
69
|
+
@property
|
|
70
|
+
def groove_residual_rms_nm(self):
|
|
71
|
+
"""RMS residual of the grooves about their average, in nm.
|
|
72
|
+
|
|
73
|
+
None when fewer than two grooves were averaged. What it measures, and
|
|
74
|
+
why `to_problem` does not use it unless asked to, is in that method's
|
|
75
|
+
docstring and in docs/theory/metrology.md section 4.
|
|
76
|
+
"""
|
|
77
|
+
return self.metrics.get('groove_residual_rms_nm')
|
|
78
|
+
|
|
69
79
|
def to_problem(self, *, coating=None, substrate=None, roughness=0.0,
|
|
70
80
|
blaze_angle=None):
|
|
71
81
|
"""This measured groove as an efficiency problem.
|
|
@@ -87,9 +97,11 @@ class BoundaryProfile:
|
|
|
87
97
|
Parameters:
|
|
88
98
|
coating, substrate: material names, resolved by
|
|
89
99
|
`gratinglab.materials` at solve time.
|
|
90
|
-
roughness: RMS surface roughness in nm
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
roughness: RMS surface roughness in nm, the Nevot-Croce sigma.
|
|
101
|
+
0.0 means "not supplied", not "smooth". It stays 0.0 unless
|
|
102
|
+
the caller passes a number, including the groove residual
|
|
103
|
+
this class measures; the note below says why that is not
|
|
104
|
+
the default.
|
|
93
105
|
blaze_angle: fitted facet angle in degrees, if the same scan was
|
|
94
106
|
also run through `core.analysis.extract_blaze_angle`. Without
|
|
95
107
|
it the scalar solver evaluates reflection on the mean surface,
|
|
@@ -98,13 +110,39 @@ class BoundaryProfile:
|
|
|
98
110
|
|
|
99
111
|
A note on roughness: `Problem.roughness` means high-spatial-frequency
|
|
100
112
|
surface roughness, the quantity with which Nevot-Croce damps a Fresnel
|
|
101
|
-
coefficient.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
coefficient. `y_std_nm` is the groove-to-groove form spread and
|
|
114
|
+
`metrics['rms_slope']` is a slope statistic, and neither is that.
|
|
115
|
+
|
|
116
|
+
`groove_residual_rms_nm` is the nearest thing this class measures: the
|
|
117
|
+
RMS of each groove about the average once registered to it, which is
|
|
118
|
+
the part of the profile that does not repeat with the period. It is
|
|
119
|
+
still not passed by default, for four reasons, each of which makes it
|
|
120
|
+
a different number from the sigma Nevot-Croce wants:
|
|
121
|
+
|
|
122
|
+
- It is measured on the row-averaged profile. Structure that varies
|
|
123
|
+
along a groove averages away over the scan lines, and for isotropic
|
|
124
|
+
roughness with a correlation length well under the scan height that
|
|
125
|
+
is nearly all of it. The number keeps only what is coherent along
|
|
126
|
+
the groove for the height of the scan, so it is a lower bound on the
|
|
127
|
+
surface's roughness in its band.
|
|
128
|
+
- Its band is not the sigma's band. It sees nothing finer than the
|
|
129
|
+
pixel pitch or the tip, a few nanometres, while the structure that
|
|
130
|
+
damps a soft X-ray reflection extends down to wavelengths near the
|
|
131
|
+
X-ray wavelength itself. At the other end it includes groove-scale
|
|
132
|
+
form error, whose correlation length is about a period, where
|
|
133
|
+
Nevot-Croce assumes a short one.
|
|
134
|
+
- Groove-to-groove form error removes light from the orders by
|
|
135
|
+
scattering it between them, a Debye-Waller factor on each order's
|
|
136
|
+
own momentum transfer, not a damping of the Fresnel coefficient at
|
|
137
|
+
the facet. Folding it into the facet reflectivity applies the wrong
|
|
138
|
+
factor to the right effect.
|
|
139
|
+
- It carries instrument terms: AFM noise divided by the square root of
|
|
140
|
+
the number of scan lines, and a floor where the surface has corners
|
|
141
|
+
sharper than a pixel (0.21 nm on the synthetic sawtooth).
|
|
142
|
+
|
|
143
|
+
A caller who accepts those can pass it, and the name then appears in
|
|
144
|
+
their own code, which is the point:
|
|
145
|
+
`profile.to_problem(roughness=profile.groove_residual_rms_nm)`.
|
|
108
146
|
"""
|
|
109
147
|
# Imported here rather than at module scope: `boundary` is reachable
|
|
110
148
|
# from the GUI preview, which has no reason to pull the solver stack in
|
|
@@ -217,6 +255,16 @@ def build_boundary_profile(data, scan_x_size, settings, orientation=None) -> Bou
|
|
|
217
255
|
smoothing_window=settings.ggp_smoothing_window)
|
|
218
256
|
|
|
219
257
|
metrics = profile_metrics(x_norm, y_norm, period_nm, n_used)
|
|
258
|
+
# The same grooves average_grooves used, chosen by the same rules.
|
|
259
|
+
residual = groove_residuals(
|
|
260
|
+
select_grooves(raw_x, flat_y, groove_centers, period_nm, margin=0.0,
|
|
261
|
+
min_half_width=settings.ggp_min_half_width),
|
|
262
|
+
x_avg, y_avg)
|
|
263
|
+
metrics['groove_residual_rms_nm'] = residual['rms_nm']
|
|
264
|
+
metrics['groove_residual_per_groove_nm'] = residual['per_groove_nm'].tolist()
|
|
265
|
+
metrics['groove_residual_max_shift_px'] = (
|
|
266
|
+
float(np.max(np.abs(residual['shift_px'])))
|
|
267
|
+
if residual['rms_nm'] is not None else None)
|
|
220
268
|
if orientation is not None:
|
|
221
269
|
metrics.update(orientation.as_metrics())
|
|
222
270
|
metrics['tip_correction'] = settings.tip_correction
|
|
@@ -275,6 +275,11 @@ class BoundaryView(QWidget):
|
|
|
275
275
|
f"+/- {m['groove_rotation_stderr_deg']:.3f} deg"
|
|
276
276
|
+ (", corrected" if m.get('groove_rotation_corrected')
|
|
277
277
|
else ", not corrected"))
|
|
278
|
+
rms = m.get('groove_residual_rms_nm')
|
|
279
|
+
# Labelled as a residual, not a roughness: it is not what to_problem
|
|
280
|
+
# applies, and the panel should not suggest otherwise.
|
|
281
|
+
residual = ("not measured, one groove" if rms is None
|
|
282
|
+
else f"{rms:.3f} nm, not applied")
|
|
278
283
|
self.metrics_label.setText(
|
|
279
284
|
f"Period : {p.period_nm:.2f} nm\n"
|
|
280
285
|
f"Grooves averaged: {p.n_used} of {p.n_grooves}{edge}\n"
|
|
@@ -282,7 +287,8 @@ class BoundaryView(QWidget):
|
|
|
282
287
|
f"Peak-to-valley : {m['peak_to_valley']:.4f} of period\n"
|
|
283
288
|
f"RMS slope : {m['rms_slope']:.4f}\n"
|
|
284
289
|
f"Max sidewall : {m['max_angle_deg']:.2f} deg\n"
|
|
285
|
-
f"Max curvature : {m['max_curvature']:.4f}
|
|
290
|
+
f"Max curvature : {m['max_curvature']:.4f}\n"
|
|
291
|
+
f"Groove residual : {residual}{rotation}{tip}")
|
|
286
292
|
|
|
287
293
|
def _show_placeholder(self, message="Load an AFM file in the Import tab"):
|
|
288
294
|
self._placeholder = message
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The controls-and-plot layout every AFM tab shares.
|
|
3
|
+
|
|
4
|
+
Each tab was a fixed 272 px column of controls in a 292 px scroll area beside
|
|
5
|
+
its plot, so long labels were crammed and nothing could be widened. A splitter
|
|
6
|
+
replaces that: the column starts wide enough for its contents, cannot be
|
|
7
|
+
dragged narrower than its widest control needs, and gives the plot every pixel
|
|
8
|
+
the window gains.
|
|
9
|
+
|
|
10
|
+
The width is one setting, not one per tab. Dragging the column in any tab
|
|
11
|
+
moves it in the others, so switching tabs never makes the plot jump, and the
|
|
12
|
+
width is saved as it is dragged, so the window opens at it next time. It is
|
|
13
|
+
kept in the same store as the theme (``theming.settings``), which the test
|
|
14
|
+
suite redirects to a scratch file.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import weakref
|
|
19
|
+
|
|
20
|
+
from . import * # noqa: F401,F403 - sets QT_API before matplotlib's shim loads
|
|
21
|
+
|
|
22
|
+
from PySide6.QtCore import Qt
|
|
23
|
+
from PySide6.QtWidgets import QFrame, QScrollArea, QSplitter, QWidget
|
|
24
|
+
|
|
25
|
+
from gratinglab.gui.qt.theming import settings
|
|
26
|
+
|
|
27
|
+
__all__ = ["controls_beside", "MIN_CONTROLS_WIDTH", "START_CONTROLS_WIDTH",
|
|
28
|
+
"WIDTH_KEY"]
|
|
29
|
+
|
|
30
|
+
#: Narrower than this and the group boxes' labels start to wrap mid-word.
|
|
31
|
+
MIN_CONTROLS_WIDTH = 260
|
|
32
|
+
#: Where the column starts, unless its contents ask for more.
|
|
33
|
+
START_CONTROLS_WIDTH = 320
|
|
34
|
+
#: Where the chosen width is remembered.
|
|
35
|
+
WIDTH_KEY = "afm/controls_width"
|
|
36
|
+
|
|
37
|
+
# Every live splitter, so a drag in one tab can move the others. Weak, so a
|
|
38
|
+
# closed window's splitters drop out on their own.
|
|
39
|
+
_SPLITTERS: "weakref.WeakSet[QSplitter]" = weakref.WeakSet()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _saved_width() -> int | None:
|
|
43
|
+
value = settings().value(WIDTH_KEY)
|
|
44
|
+
try:
|
|
45
|
+
return int(value) if value is not None else None
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
return None # a hand-edited or corrupt value: fall back to the default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _follow(source: QSplitter) -> None:
|
|
51
|
+
"""Remember ``source``'s column width and give it to every other tab."""
|
|
52
|
+
width = source.sizes()[0]
|
|
53
|
+
store = settings()
|
|
54
|
+
store.setValue(WIDTH_KEY, width)
|
|
55
|
+
store.sync()
|
|
56
|
+
for other in list(_SPLITTERS):
|
|
57
|
+
if other is source:
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
total = sum(other.sizes())
|
|
61
|
+
other.blockSignals(True)
|
|
62
|
+
other.setSizes([width, max(1, total - width)])
|
|
63
|
+
other.blockSignals(False)
|
|
64
|
+
except RuntimeError:
|
|
65
|
+
# Its window was closed and Qt has deleted it, while the Python
|
|
66
|
+
# wrapper waits for collection.
|
|
67
|
+
_SPLITTERS.discard(other)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def controls_beside(controls: QWidget, plot: QWidget) -> QSplitter:
|
|
71
|
+
"""A splitter holding the scrolling ``controls`` column and the ``plot``."""
|
|
72
|
+
scroll = QScrollArea()
|
|
73
|
+
scroll.setWidget(controls)
|
|
74
|
+
scroll.setWidgetResizable(True)
|
|
75
|
+
scroll.setFrameShape(QFrame.NoFrame)
|
|
76
|
+
# Width is the splitter's to decide; only the height should ever scroll.
|
|
77
|
+
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
78
|
+
|
|
79
|
+
gutter = scroll.verticalScrollBar().sizeHint().width()
|
|
80
|
+
floor = max(MIN_CONTROLS_WIDTH, controls.minimumSizeHint().width() + gutter)
|
|
81
|
+
scroll.setMinimumWidth(floor)
|
|
82
|
+
|
|
83
|
+
splitter = QSplitter(Qt.Horizontal)
|
|
84
|
+
splitter.setChildrenCollapsible(False)
|
|
85
|
+
splitter.addWidget(scroll)
|
|
86
|
+
splitter.addWidget(plot)
|
|
87
|
+
splitter.setStretchFactor(0, 0)
|
|
88
|
+
splitter.setStretchFactor(1, 1)
|
|
89
|
+
# A remembered width wins, but never below what this tab's controls need.
|
|
90
|
+
start = max(_saved_width() or START_CONTROLS_WIDTH, floor)
|
|
91
|
+
splitter.setSizes([start, max(1, 4 * start)])
|
|
92
|
+
|
|
93
|
+
_SPLITTERS.add(splitter)
|
|
94
|
+
splitter.splitterMoved.connect(lambda *_: _follow(splitter))
|
|
95
|
+
return splitter
|
|
@@ -25,6 +25,16 @@ def write_profile_metrics(path, metrics):
|
|
|
25
25
|
f.write(f"RMS slope: {metrics['rms_slope']:.4f} (normalized units)\n")
|
|
26
26
|
f.write(f"Max slope magnitude: {metrics['max_slope']:.4f}\n")
|
|
27
27
|
f.write(f"Max curvature: {metrics['max_curvature']:.6f} (normalized units)\n")
|
|
28
|
+
# Written only by a pipeline that measures it, so a metrics dict from
|
|
29
|
+
# before it existed still writes. Not applied as roughness: see
|
|
30
|
+
# BoundaryProfile.to_problem.
|
|
31
|
+
if 'groove_residual_rms_nm' in metrics:
|
|
32
|
+
rms = metrics['groove_residual_rms_nm']
|
|
33
|
+
if rms is None:
|
|
34
|
+
f.write("Groove residual RMS: not measured (one groove)\n")
|
|
35
|
+
else:
|
|
36
|
+
f.write(f"Groove residual RMS: {rms:.3f} nm (about the average, "
|
|
37
|
+
f"not applied as roughness)\n")
|
|
28
38
|
# Present only when the pipeline ran a tip correction. Stated even so:
|
|
29
39
|
# a corrected depth and an uncorrected one are different measurements,
|
|
30
40
|
# and this sidecar is where a reader learns which this file holds.
|