gratinglab 0.1.7__tar.gz → 0.1.9__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.
Files changed (156) hide show
  1. {gratinglab-0.1.7 → gratinglab-0.1.9}/PKG-INFO +96 -15
  2. gratinglab-0.1.7/src/gratinglab.egg-info/PKG-INFO → gratinglab-0.1.9/README.md +88 -51
  3. {gratinglab-0.1.7 → gratinglab-0.1.9}/pyproject.toml +35 -3
  4. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/__init__.py +1 -1
  5. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/app.py +1 -1
  6. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/icon.py +1 -1
  7. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/core/processing.py +26 -8
  8. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/icon.py +1 -1
  9. gratinglab-0.1.9/src/gratinglab/metrology/wiki/flattening.md +245 -0
  10. gratinglab-0.1.7/README.md → gratinglab-0.1.9/src/gratinglab.egg-info/PKG-INFO +132 -12
  11. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab.egg-info/SOURCES.txt +1 -0
  12. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab.egg-info/requires.txt +6 -1
  13. gratinglab-0.1.9/tests/test_changelog.py +34 -0
  14. gratinglab-0.1.7/src/gratinglab/metrology/wiki/flattening.md +0 -109
  15. {gratinglab-0.1.7 → gratinglab-0.1.9}/LICENSE +0 -0
  16. {gratinglab-0.1.7 → gratinglab-0.1.9}/setup.cfg +0 -0
  17. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/checks.py +0 -0
  18. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/compare.py +0 -0
  19. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/convergence.py +0 -0
  20. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/geometry.py +0 -0
  21. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/__init__.py +0 -0
  22. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/diagram.py +0 -0
  23. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/diagram3d.py +0 -0
  24. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/docs.py +0 -0
  25. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/integral_options.py +0 -0
  26. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/mathtext.py +0 -0
  27. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/orders.py +0 -0
  28. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/provenance.py +0 -0
  29. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/__init__.py +0 -0
  30. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/gc_guard.py +0 -0
  31. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
  32. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
  33. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/integral_tab.py +0 -0
  34. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/main_window.py +0 -0
  35. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
  36. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
  37. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
  38. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/setup_tab.py +0 -0
  39. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/sizing.py +0 -0
  40. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
  41. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/qt/worker.py +0 -0
  42. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/richtext.py +0 -0
  43. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/scalar_options.py +0 -0
  44. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/gui/state.py +0 -0
  45. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/illumination.py +0 -0
  46. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/io/__init__.py +0 -0
  47. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/io/boundary.py +0 -0
  48. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/io/efficiency_table.py +0 -0
  49. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/io/ggp.py +0 -0
  50. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/materials/__init__.py +0 -0
  51. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/materials/data/SOURCES.md +0 -0
  52. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/materials/fresnel.py +0 -0
  53. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/materials/optical.py +0 -0
  54. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/materials/refractiveindex.py +0 -0
  55. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/__init__.py +0 -0
  56. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/analyzer.py +0 -0
  57. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/boundary/__init__.py +0 -0
  58. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/boundary/average.py +0 -0
  59. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
  60. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/boundary/swaths.py +0 -0
  61. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/cli.py +0 -0
  62. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/config.py +0 -0
  63. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/core/__init__.py +0 -0
  64. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/core/analysis.py +0 -0
  65. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/core/image_flatten.py +0 -0
  66. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/core/tip.py +0 -0
  67. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/data/__init__.py +0 -0
  68. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/data/aggregation.py +0 -0
  69. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/__init__.py +0 -0
  70. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
  71. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/app.py +0 -0
  72. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
  73. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
  74. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
  75. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
  76. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
  77. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
  78. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
  79. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
  80. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/gui/state.py +0 -0
  81. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/io/__init__.py +0 -0
  82. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/io/file_io.py +0 -0
  83. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/io/metrics.py +0 -0
  84. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/io/spm.py +0 -0
  85. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/settings.py +0 -0
  86. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/stats/__init__.py +0 -0
  87. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/stats/analysis.py +0 -0
  88. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/stats/icc.py +0 -0
  89. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/visualization/__init__.py +0 -0
  90. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
  91. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/visualization/profiles.py +0 -0
  92. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/visualization/statistics.py +0 -0
  93. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/__init__.py +0 -0
  94. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
  95. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
  96. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
  97. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/overview.md +0 -0
  98. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
  99. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
  100. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
  101. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
  102. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/metrology/workflows.py +0 -0
  103. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/problem.py +0 -0
  104. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/profiles.py +0 -0
  105. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/resolution.py +0 -0
  106. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/result.py +0 -0
  107. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/__init__.py +0 -0
  108. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/base.py +0 -0
  109. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/__init__.py +0 -0
  110. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_boundary.py +0 -0
  111. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_core.py +0 -0
  112. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_finite.py +0 -0
  113. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_greens.py +0 -0
  114. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_kernels.py +0 -0
  115. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
  116. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab/solvers/scalar.py +0 -0
  117. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab.egg-info/dependency_links.txt +0 -0
  118. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab.egg-info/entry_points.txt +0 -0
  119. {gratinglab-0.1.7 → gratinglab-0.1.9}/src/gratinglab.egg-info/top_level.txt +0 -0
  120. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_checks.py +0 -0
  121. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_compare.py +0 -0
  122. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_convergence.py +0 -0
  123. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_cross_method.py +0 -0
  124. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_efficiency_table.py +0 -0
  125. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_fresnel.py +0 -0
  126. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_geometry.py +0 -0
  127. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_ggp.py +0 -0
  128. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_diagram.py +0 -0
  129. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_diagram3d.py +0 -0
  130. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_docs.py +0 -0
  131. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_integral_options.py +0 -0
  132. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_mathtext.py +0 -0
  133. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_orders.py +0 -0
  134. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_provenance.py +0 -0
  135. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_qt.py +0 -0
  136. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_richtext.py +0 -0
  137. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_scalar_options.py +0 -0
  138. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_gui_state.py +0 -0
  139. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_illumination.py +0 -0
  140. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_core.py +0 -0
  141. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_corpus.py +0 -0
  142. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_finite.py +0 -0
  143. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_greens.py +0 -0
  144. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_kernels.py +0 -0
  145. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_operators.py +0 -0
  146. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_integral_solver.py +0 -0
  147. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_io_boundary.py +0 -0
  148. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_materials.py +0 -0
  149. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_perturbation.py +0 -0
  150. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_profiles.py +0 -0
  151. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_refractiveindex.py +0 -0
  152. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_resolution.py +0 -0
  153. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_result.py +0 -0
  154. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_scalar.py +0 -0
  155. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_solver_conformance.py +0 -0
  156. {gratinglab-0.1.7 → gratinglab-0.1.9}/tests/test_solver_progress.py +0 -0
@@ -1,17 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gratinglab
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Open platform for diffraction grating groove metrology and efficiency analysis
5
5
  Author: Jake McCoy
6
6
  License: BSD-3-Clause
7
7
  Project-URL: Homepage, https://github.com/jamccoy/gratinglab
8
- Keywords: diffraction,grating,efficiency,rcwa,x-ray,spectroscopy,afm,metrology,blaze,pcgrate
8
+ Project-URL: Changelog, https://github.com/jamccoy/gratinglab/blob/master/CHANGELOG.md
9
+ Keywords: diffraction,grating,diffraction grating,diffraction efficiency,optics,rcwa,integral method,spectroscopy,afm,metrology,blaze,groove profile,ultraviolet,infrared,x-ray
9
10
  Classifier: Development Status :: 2 - Pre-Alpha
10
11
  Classifier: Intended Audience :: Science/Research
11
12
  Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
12
14
  Classifier: Programming Language :: Python :: 3.11
13
15
  Classifier: Programming Language :: Python :: 3.12
14
16
  Classifier: Topic :: Scientific/Engineering :: Physics
17
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
18
+ Classifier: Topic :: Scientific/Engineering :: Visualization
15
19
  Requires-Python: >=3.11
16
20
  Description-Content-Type: text/markdown
17
21
  License-File: LICENSE
@@ -27,7 +31,8 @@ Requires-Dist: pandas>=2.1; extra == "dev"
27
31
  Requires-Dist: ruff>=0.16; extra == "dev"
28
32
  Provides-Extra: gui
29
33
  Requires-Dist: matplotlib>=3.8; extra == "gui"
30
- Requires-Dist: PySide6-Essentials>=6.7; extra == "gui"
34
+ Requires-Dist: PySide6-Essentials!=6.12.0,>=6.7; python_version < "3.12" and extra == "gui"
35
+ Requires-Dist: PySide6-Essentials>=6.7; python_version >= "3.12" and extra == "gui"
31
36
  Requires-Dist: endstation[qt]>=0.2.2; extra == "gui"
32
37
  Provides-Extra: metrology
33
38
  Requires-Dist: matplotlib>=3.9; extra == "metrology"
@@ -44,7 +49,7 @@ Dynamic: license-file
44
49
  [![tests](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml/badge.svg)](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml)
45
50
  [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue)](https://github.com/jamccoy/gratinglab/blob/master/LICENSE)
46
51
 
47
- **[PyPI](https://pypi.org/project/gratinglab/)** · **[Documentation](https://github.com/jamccoy/gratinglab/wiki)** · **[Releases](https://github.com/jamccoy/gratinglab/releases)** · **[Issues](https://github.com/jamccoy/gratinglab/issues)**
52
+ **[PyPI](https://pypi.org/project/gratinglab/)** · **[Documentation](https://github.com/jamccoy/gratinglab/wiki)** · **[Changelog](https://github.com/jamccoy/gratinglab/blob/master/CHANGELOG.md)** · **[Releases](https://github.com/jamccoy/gratinglab/releases)** · **[Issues](https://github.com/jamccoy/gratinglab/issues)**
48
53
 
49
54
  gratinglab is a Python package for measuring the groove profiles of diffraction
50
55
  gratings from atomic force microscope (AFM) scans and for computing the diffraction
@@ -225,12 +230,14 @@ does not install one. A subpackage that needs an optional extra names it when it
225
230
  missing, instead of failing with a bare `ModuleNotFoundError`.
226
231
 
227
232
  Apart from numpy, scipy and pydantic, the only dependency is
228
- [endstation](https://github.com/jamccoy/endstation), a library of shared components for
229
- desktop scientific applications by the same author. gratinglab uses its guards for
230
- optional extras (the messages described above), form validation, Qt layout sizing,
231
- test helpers, and the tools that build the icon and the macOS application and publish
232
- the wiki. The core of endstation has no dependencies of its own, so it adds nothing to
233
- a solver-only install; its Qt components are installed only with the `gui` extra.
233
+ **[endstation](https://pypi.org/project/endstation/)**
234
+ ([source](https://github.com/jamccoy/endstation)), a library of shared components for
235
+ desktop scientific applications by the same author, which gratinglab is built on.
236
+ gratinglab uses its guards for optional extras (the messages described above), form
237
+ validation, Qt layout sizing, test helpers, and the tools that build the icon and the
238
+ macOS application and publish the wiki. The core of endstation has no dependencies of
239
+ its own, so it adds nothing to a solver-only install; its Qt components are installed
240
+ only with the `gui` extra.
234
241
 
235
242
  ### Where the measurement data lives
236
243
 
@@ -293,9 +300,14 @@ contributed backend appears in the window once it is registered.
293
300
 
294
301
  The Help menu contains the theory pages, rendered with typeset equations and tables.
295
302
 
296
- A macOS `.app` can be built with `python tools/make_app.py` (macOS 13 or later, the
297
- minimum supported by the PySide6 wheels), and the icon is regenerated with
298
- `python tools/make_icon.py`.
303
+ A macOS `.app` can be built for either window with `python tools/apps.py bundle --app
304
+ gratinglab` (or `--app metrology`), on macOS 13 or later, the minimum supported by the
305
+ PySide6 wheels. `--dest ~/Applications` puts it where Finder will list it. The bundle
306
+ is a stub that execs the console script in this checkout's virtualenv rather than a
307
+ frozen application, so it is not portable to another machine; `python tools/apps.py
308
+ check --app gratinglab` reports when that virtualenv has moved, which is the one
309
+ failure a bundle cannot show from the outside. `python tools/apps.py icon --app
310
+ gratinglab` regenerates the icon from the artwork in `gratinglab.gui.icon`.
299
311
 
300
312
  ## Documentation
301
313
 
@@ -367,6 +379,75 @@ The test suite relies on three practices in particular:
367
379
  .venv/bin/mutmut run
368
380
  ```
369
381
 
370
- ## License
382
+ ## License, copyright and reuse
371
383
 
372
- BSD-3-Clause.
384
+ gratinglab is released under the
385
+ [BSD 3-Clause License](https://github.com/jamccoy/gratinglab/blob/master/LICENSE),
386
+ copyright (c) 2026 Jake McCoy. What follows is a plain summary for convenience; the
387
+ `LICENSE` file is what governs, and nothing here is legal advice.
388
+
389
+ **What you may do.** Use, modify and redistribute the code, in source or binary form,
390
+ for any purpose, including commercial use and inside closed-source work. There is no
391
+ copyleft obligation: software that imports gratinglab does not become BSD-licensed,
392
+ and you are not required to publish your modifications.
393
+
394
+ **What you must do.** Retain the copyright notice, the list of conditions and the
395
+ disclaimer when you redistribute — in the source for a source distribution, and in the
396
+ documentation or other accompanying materials for a binary one. The third clause
397
+ separately forbids using the author's or contributors' names to endorse or promote a
398
+ derived product without prior written permission.
399
+
400
+ **What you do not get.** The software is provided as is, without warranty of any kind,
401
+ and the author is not liable for any use of it. That disclaimer carries more weight
402
+ here than it does for most packages, because the efficiency solvers are experimental
403
+ (see Status): a number this software produces is evidence to be checked against
404
+ another method, not a result to be relied on because a computer printed it.
405
+
406
+ Contributions are accepted under the same licence. A pull request is taken as agreement
407
+ that the contribution may be distributed under BSD-3-Clause.
408
+
409
+ ### Citing it
410
+
411
+ [`CITATION.cff`](https://github.com/jamccoy/gratinglab/blob/master/CITATION.cff) is
412
+ the citation of record, and GitHub's "Cite this repository" renders it as BibTeX or
413
+ APA. It names the version, which is checked against `gratinglab.__version__` on every
414
+ release, so a citation generated from it points at the code that produced the number
415
+ rather than at whatever is current. If a result depends on a solver backend or on an
416
+ optical constant table, cite those too: the method's own reference is in
417
+ [`docs/references.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/references.md),
418
+ and `Provenance` records what a run actually used.
419
+
420
+ ### The licences you inherit by installing it
421
+
422
+ | Installed with | Package | Licence |
423
+ |---|---|---|
424
+ | core | numpy, scipy | BSD-3-Clause |
425
+ | core | pydantic | MIT |
426
+ | core | [endstation](https://pypi.org/project/endstation/) | BSD-3-Clause |
427
+ | `[gui]`, `[metrology]` | matplotlib | PSF-based, BSD-compatible |
428
+ | `[gui]` | PySide6-Essentials | LGPL-3.0 |
429
+ | `[refractiveindex]` | PyYAML | MIT |
430
+
431
+ A solver-only install is permissive throughout. The `gui` extra is the one worth
432
+ knowing about: Qt for Python is LGPL-3.0, which is the reason this project depends on
433
+ PySide6 and not on PyQt6, whose licence is GPL and would reach your own code.
434
+ Importing an LGPL library dynamically does not change the licence of what you write,
435
+ but redistributing a bundled application that ships Qt inside it brings LGPL
436
+ obligations for that component — chiefly your users' right to substitute their own
437
+ build of it. Running the window yourself, or installing either extra from PyPI, raises
438
+ no such question.
439
+
440
+ ### What this licence does not cover
441
+
442
+ **Optical constant tables.** None are distributed with the package, and a table you
443
+ add is governed by the terms of whoever published it, not by this licence. CXRO and
444
+ refractiveindex.info each state their own, and a table exported from another program
445
+ carries whatever that program's terms attach to it.
446
+ [`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
447
+ records where each kind comes from and why none ships here.
448
+
449
+ **Measurement data.** AFM scans and the reference efficiency corpus are the research
450
+ group's data and are deliberately absent from the repository, as described above under
451
+ Install. The code is open; the measurements it was validated against are not ours to
452
+ publish, and the documentation says which results are therefore not reproducible from
453
+ a bare clone.
@@ -1,42 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: gratinglab
3
- Version: 0.1.7
4
- Summary: Open platform for diffraction grating groove metrology and efficiency analysis
5
- Author: Jake McCoy
6
- License: BSD-3-Clause
7
- Project-URL: Homepage, https://github.com/jamccoy/gratinglab
8
- Keywords: diffraction,grating,efficiency,rcwa,x-ray,spectroscopy,afm,metrology,blaze,pcgrate
9
- Classifier: Development Status :: 2 - Pre-Alpha
10
- Classifier: Intended Audience :: Science/Research
11
- Classifier: License :: OSI Approved :: BSD License
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Topic :: Scientific/Engineering :: Physics
15
- Requires-Python: >=3.11
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: numpy>=1.26
19
- Requires-Dist: scipy>=1.11
20
- Requires-Dist: pydantic>=2.6
21
- Requires-Dist: endstation>=0.2.2
22
- Provides-Extra: dev
23
- Requires-Dist: pytest>=7; extra == "dev"
24
- Requires-Dist: pytest-qt>=4.5; extra == "dev"
25
- Requires-Dist: matplotlib>=3.8; extra == "dev"
26
- Requires-Dist: pandas>=2.1; extra == "dev"
27
- Requires-Dist: ruff>=0.16; extra == "dev"
28
- Provides-Extra: gui
29
- Requires-Dist: matplotlib>=3.8; extra == "gui"
30
- Requires-Dist: PySide6-Essentials>=6.7; extra == "gui"
31
- Requires-Dist: endstation[qt]>=0.2.2; extra == "gui"
32
- Provides-Extra: metrology
33
- Requires-Dist: matplotlib>=3.9; extra == "metrology"
34
- Provides-Extra: refractiveindex
35
- Requires-Dist: pyyaml>=6; extra == "refractiveindex"
36
- Provides-Extra: mut
37
- Requires-Dist: mutmut<3.3,>=3.2; extra == "mut"
38
- Dynamic: license-file
39
-
40
1
  # gratinglab
41
2
 
42
3
  [![PyPI](https://img.shields.io/pypi/v/gratinglab?cacheSeconds=3600)](https://pypi.org/project/gratinglab/)
@@ -44,7 +5,7 @@ Dynamic: license-file
44
5
  [![tests](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml/badge.svg)](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml)
45
6
  [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue)](https://github.com/jamccoy/gratinglab/blob/master/LICENSE)
46
7
 
47
- **[PyPI](https://pypi.org/project/gratinglab/)** · **[Documentation](https://github.com/jamccoy/gratinglab/wiki)** · **[Releases](https://github.com/jamccoy/gratinglab/releases)** · **[Issues](https://github.com/jamccoy/gratinglab/issues)**
8
+ **[PyPI](https://pypi.org/project/gratinglab/)** · **[Documentation](https://github.com/jamccoy/gratinglab/wiki)** · **[Changelog](https://github.com/jamccoy/gratinglab/blob/master/CHANGELOG.md)** · **[Releases](https://github.com/jamccoy/gratinglab/releases)** · **[Issues](https://github.com/jamccoy/gratinglab/issues)**
48
9
 
49
10
  gratinglab is a Python package for measuring the groove profiles of diffraction
50
11
  gratings from atomic force microscope (AFM) scans and for computing the diffraction
@@ -225,12 +186,14 @@ does not install one. A subpackage that needs an optional extra names it when it
225
186
  missing, instead of failing with a bare `ModuleNotFoundError`.
226
187
 
227
188
  Apart from numpy, scipy and pydantic, the only dependency is
228
- [endstation](https://github.com/jamccoy/endstation), a library of shared components for
229
- desktop scientific applications by the same author. gratinglab uses its guards for
230
- optional extras (the messages described above), form validation, Qt layout sizing,
231
- test helpers, and the tools that build the icon and the macOS application and publish
232
- the wiki. The core of endstation has no dependencies of its own, so it adds nothing to
233
- a solver-only install; its Qt components are installed only with the `gui` extra.
189
+ **[endstation](https://pypi.org/project/endstation/)**
190
+ ([source](https://github.com/jamccoy/endstation)), a library of shared components for
191
+ desktop scientific applications by the same author, which gratinglab is built on.
192
+ gratinglab uses its guards for optional extras (the messages described above), form
193
+ validation, Qt layout sizing, test helpers, and the tools that build the icon and the
194
+ macOS application and publish the wiki. The core of endstation has no dependencies of
195
+ its own, so it adds nothing to a solver-only install; its Qt components are installed
196
+ only with the `gui` extra.
234
197
 
235
198
  ### Where the measurement data lives
236
199
 
@@ -293,9 +256,14 @@ contributed backend appears in the window once it is registered.
293
256
 
294
257
  The Help menu contains the theory pages, rendered with typeset equations and tables.
295
258
 
296
- A macOS `.app` can be built with `python tools/make_app.py` (macOS 13 or later, the
297
- minimum supported by the PySide6 wheels), and the icon is regenerated with
298
- `python tools/make_icon.py`.
259
+ A macOS `.app` can be built for either window with `python tools/apps.py bundle --app
260
+ gratinglab` (or `--app metrology`), on macOS 13 or later, the minimum supported by the
261
+ PySide6 wheels. `--dest ~/Applications` puts it where Finder will list it. The bundle
262
+ is a stub that execs the console script in this checkout's virtualenv rather than a
263
+ frozen application, so it is not portable to another machine; `python tools/apps.py
264
+ check --app gratinglab` reports when that virtualenv has moved, which is the one
265
+ failure a bundle cannot show from the outside. `python tools/apps.py icon --app
266
+ gratinglab` regenerates the icon from the artwork in `gratinglab.gui.icon`.
299
267
 
300
268
  ## Documentation
301
269
 
@@ -367,6 +335,75 @@ The test suite relies on three practices in particular:
367
335
  .venv/bin/mutmut run
368
336
  ```
369
337
 
370
- ## License
338
+ ## License, copyright and reuse
339
+
340
+ gratinglab is released under the
341
+ [BSD 3-Clause License](https://github.com/jamccoy/gratinglab/blob/master/LICENSE),
342
+ copyright (c) 2026 Jake McCoy. What follows is a plain summary for convenience; the
343
+ `LICENSE` file is what governs, and nothing here is legal advice.
344
+
345
+ **What you may do.** Use, modify and redistribute the code, in source or binary form,
346
+ for any purpose, including commercial use and inside closed-source work. There is no
347
+ copyleft obligation: software that imports gratinglab does not become BSD-licensed,
348
+ and you are not required to publish your modifications.
349
+
350
+ **What you must do.** Retain the copyright notice, the list of conditions and the
351
+ disclaimer when you redistribute — in the source for a source distribution, and in the
352
+ documentation or other accompanying materials for a binary one. The third clause
353
+ separately forbids using the author's or contributors' names to endorse or promote a
354
+ derived product without prior written permission.
355
+
356
+ **What you do not get.** The software is provided as is, without warranty of any kind,
357
+ and the author is not liable for any use of it. That disclaimer carries more weight
358
+ here than it does for most packages, because the efficiency solvers are experimental
359
+ (see Status): a number this software produces is evidence to be checked against
360
+ another method, not a result to be relied on because a computer printed it.
361
+
362
+ Contributions are accepted under the same licence. A pull request is taken as agreement
363
+ that the contribution may be distributed under BSD-3-Clause.
364
+
365
+ ### Citing it
366
+
367
+ [`CITATION.cff`](https://github.com/jamccoy/gratinglab/blob/master/CITATION.cff) is
368
+ the citation of record, and GitHub's "Cite this repository" renders it as BibTeX or
369
+ APA. It names the version, which is checked against `gratinglab.__version__` on every
370
+ release, so a citation generated from it points at the code that produced the number
371
+ rather than at whatever is current. If a result depends on a solver backend or on an
372
+ optical constant table, cite those too: the method's own reference is in
373
+ [`docs/references.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/references.md),
374
+ and `Provenance` records what a run actually used.
375
+
376
+ ### The licences you inherit by installing it
377
+
378
+ | Installed with | Package | Licence |
379
+ |---|---|---|
380
+ | core | numpy, scipy | BSD-3-Clause |
381
+ | core | pydantic | MIT |
382
+ | core | [endstation](https://pypi.org/project/endstation/) | BSD-3-Clause |
383
+ | `[gui]`, `[metrology]` | matplotlib | PSF-based, BSD-compatible |
384
+ | `[gui]` | PySide6-Essentials | LGPL-3.0 |
385
+ | `[refractiveindex]` | PyYAML | MIT |
386
+
387
+ A solver-only install is permissive throughout. The `gui` extra is the one worth
388
+ knowing about: Qt for Python is LGPL-3.0, which is the reason this project depends on
389
+ PySide6 and not on PyQt6, whose licence is GPL and would reach your own code.
390
+ Importing an LGPL library dynamically does not change the licence of what you write,
391
+ but redistributing a bundled application that ships Qt inside it brings LGPL
392
+ obligations for that component — chiefly your users' right to substitute their own
393
+ build of it. Running the window yourself, or installing either extra from PyPI, raises
394
+ no such question.
395
+
396
+ ### What this licence does not cover
397
+
398
+ **Optical constant tables.** None are distributed with the package, and a table you
399
+ add is governed by the terms of whoever published it, not by this licence. CXRO and
400
+ refractiveindex.info each state their own, and a table exported from another program
401
+ carries whatever that program's terms attach to it.
402
+ [`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
403
+ records where each kind comes from and why none ships here.
371
404
 
372
- BSD-3-Clause.
405
+ **Measurement data.** AFM scans and the reference efficiency corpus are the research
406
+ group's data and are deliberately absent from the repository, as described above under
407
+ Install. The code is open; the measurements it was validated against are not ours to
408
+ publish, and the documentation says which results are therefore not reproducible from
409
+ a bare clone.
@@ -14,15 +14,33 @@ readme = "README.md"
14
14
  requires-python = ">=3.11"
15
15
  license = { text = "BSD-3-Clause" }
16
16
  authors = [{ name = "Jake McCoy" }]
17
- keywords = ["diffraction", "grating", "efficiency", "rcwa", "x-ray", "spectroscopy",
18
- "afm", "metrology", "blaze", "pcgrate"]
17
+ # Wavelength-neutral on purpose. The off-plane work this package grew out of is
18
+ # soft x-ray, and that band is still named, but nothing in the solvers or the
19
+ # metrology assumes one: the grating equation carries no band, and the
20
+ # materials layer reads CXRO x-ray tables and refractiveindex.info
21
+ # ultraviolet-to-infrared tables alike. Leading with "x-ray" told a reader the
22
+ # in-plane half of the package was not meant for them.
23
+ #
24
+ # "pcgrate" is deliberately absent. It is a program this project validates
25
+ # against, not a feature of this one, and a search for it should not land here.
26
+ keywords = ["diffraction", "grating", "diffraction grating", "diffraction efficiency",
27
+ "optics", "rcwa", "integral method", "spectroscopy", "afm", "metrology",
28
+ "blaze", "groove profile", "ultraviolet", "infrared", "x-ray"]
29
+ # Only classifiers from the trove list, which PyPI rejects uploads over. Note
30
+ # that there is no `Topic :: Scientific/Engineering :: Optics`; Physics is the
31
+ # nearest, with Information Analysis for the metrology and Visualization for
32
+ # the windows. No Qt environment classifier: a classifier cannot be made
33
+ # conditional on an extra, and a default install pulls no toolkit at all.
19
34
  classifiers = [
20
35
  "Development Status :: 2 - Pre-Alpha",
21
36
  "Intended Audience :: Science/Research",
22
37
  "License :: OSI Approved :: BSD License",
38
+ "Operating System :: OS Independent",
23
39
  "Programming Language :: Python :: 3.11",
24
40
  "Programming Language :: Python :: 3.12",
25
41
  "Topic :: Scientific/Engineering :: Physics",
42
+ "Topic :: Scientific/Engineering :: Information Analysis",
43
+ "Topic :: Scientific/Engineering :: Visualization",
26
44
  ]
27
45
  dependencies = [
28
46
  "numpy>=1.26",
@@ -48,7 +66,20 @@ dev = ["pytest>=7", "pytest-qt>=4.5", "matplotlib>=3.8", "pandas>=2.1", "ruff>=0
48
66
  #
49
67
  # PySide6 (LGPLv3) rather than PyQt6 (GPL): this package is BSD-3, and an
50
68
  # optional dynamically-linked dependency keeps it that way.
51
- gui = ["matplotlib>=3.8", "PySide6-Essentials>=6.7", "endstation[qt]>=0.2.2"]
69
+ #
70
+ # PySide6 6.12.0 on Python 3.11 aborts the interpreter at exit (Fatal Python
71
+ # error: bool_dealloc) after heavy GUI use: it releases a reference to True or
72
+ # False that it does not own, which on 3.11 eventually frees the object during
73
+ # finalization. On 3.12 and later True and False are immortal and the bug is
74
+ # harmless. Measured in CI on 2026-10-22: 6.9.3, 6.10.3 and 6.11.2 passed twice
75
+ # each and 6.12.0 crashed twice. Only that release is excluded, and only on 3.11,
76
+ # so a fixed 6.12.x is taken as soon as it exists.
77
+ gui = [
78
+ "matplotlib>=3.8",
79
+ "PySide6-Essentials>=6.7,!=6.12.0; python_version < '3.12'",
80
+ "PySide6-Essentials>=6.7; python_version >= '3.12'",
81
+ "endstation[qt]>=0.2.2",
82
+ ]
52
83
  # Groove metrology: reading AFM scans and averaging them into a boundary
53
84
  # profile. An extra for the same reason the GUI is one -- `pip install
54
85
  # gratinglab` must stay numpy/scipy/pydantic, so a solver-only user never
@@ -76,6 +107,7 @@ gratinglab-metrology = "gratinglab.metrology.cli:main"
76
107
 
77
108
  [project.urls]
78
109
  Homepage = "https://github.com/jamccoy/gratinglab"
110
+ Changelog = "https://github.com/jamccoy/gratinglab/blob/master/CHANGELOG.md"
79
111
 
80
112
  # Solver backends register here. A backend may live in its own distribution.
81
113
  [project.entry-points."gratinglab.solvers"]
@@ -21,7 +21,7 @@ from .illumination import Illumination, Polarization
21
21
  from .resolution import ResolvingPower, resolving_power
22
22
  from .result import EfficiencyScan, OrderEfficiency, Provenance
23
23
 
24
- __version__ = "0.1.7"
24
+ __version__ = "0.1.9"
25
25
 
26
26
  __all__ = [
27
27
  "Illumination",
@@ -58,7 +58,7 @@ def _bring_to_front(window) -> None:
58
58
  The fallback was therefore a `subprocess.run` on every launch, wrapped in
59
59
  a bare `except`, which did nothing in the one case it was written for, and
60
60
  it has been removed. The supported way to bring the window forward when
61
- starting from a shell is to launch the bundle, which `tools/make_app.py`
61
+ starting from a shell is to launch the bundle, which `tools/apps.py bundle`
62
62
  builds and the README describes.
63
63
  """
64
64
  window.raise_()
@@ -9,7 +9,7 @@ is what this software computes. It uses a few thick elements so that it
9
9
  remains legible at 16 px, the size at which icons most often become
10
10
  unreadable.
11
11
 
12
- ``tools/make_icon.py`` turns this into a macOS ``.icns``.
12
+ ``tools/apps.py icon`` turns this into a macOS ``.icns``.
13
13
  """
14
14
 
15
15
  from __future__ import annotations
@@ -186,7 +186,9 @@ def flatten_profile(x, y, method='linear', poly_order=2, exclude_edges=0.0,
186
186
  Parameters:
187
187
  method: 'linear', 'polynomial', 'groove_peaks', or 'level_grooves'
188
188
  poly_order: polynomial order (for 'polynomial' and 'level_grooves' methods)
189
- exclude_edges: fraction of data to exclude from edges (0.0 to 0.5)
189
+ exclude_edges: fraction of data to exclude from edges (0.0 to 0.5).
190
+ For 'level_grooves' it is skipped, with a warning, when excluding
191
+ the edges would leave too few features to fit the requested order.
190
192
  feature: for 'level_grooves': 'peaks', 'troughs', or 'both'
191
193
  period_nm: estimated period for finding grooves (for 'level_grooves')
192
194
 
@@ -262,13 +264,29 @@ def flatten_profile(x, y, method='linear', poly_order=2, exclude_edges=0.0,
262
264
  x_min = x[0] + exclude_edges * (x[-1] - x[0])
263
265
  x_max = x[-1] - exclude_edges * (x[-1] - x[0])
264
266
  mask = (feature_x >= x_min) & (feature_x <= x_max)
265
- feature_x = feature_x[mask]
266
- feature_y = feature_y[mask]
267
-
268
- if len(feature_x) < 2:
269
- print(" Warning: Not enough features after edge exclusion, using all")
270
-
271
- poly_coeffs = np.polyfit(feature_x, feature_y, poly_order)
267
+ kept = int(np.count_nonzero(mask))
268
+ # Edge exclusion is meant to improve the fit, so it must not be
269
+ # allowed to starve it. This used to keep the remnant and fit
270
+ # that while printing "using all": with two features left and a
271
+ # quadratic asked for, the background was rank-deficient and
272
+ # still looked like a fit.
273
+ if kept >= poly_order + 1:
274
+ feature_x = feature_x[mask]
275
+ feature_y = feature_y[mask]
276
+ else:
277
+ print(f" Warning: edge exclusion leaves {kept} feature(s), "
278
+ f"too few for a degree-{poly_order} background; "
279
+ "using all of them")
280
+
281
+ # A degree-k polynomial needs k+1 points to be determined. Reaching
282
+ # here guarantees two, so the order is reduced rather than letting
283
+ # polyfit return a rank-deficient answer behind a RankWarning.
284
+ order = min(poly_order, len(feature_x) - 1)
285
+ if order < poly_order:
286
+ print(f" Warning: {len(feature_x)} feature(s) support only a "
287
+ f"degree-{order} background, not degree-{poly_order}")
288
+
289
+ poly_coeffs = np.polyfit(feature_x, feature_y, order)
272
290
  background = np.polyval(poly_coeffs, x)
273
291
  else:
274
292
  raise ValueError(f"Unknown flattening method: {method}")
@@ -19,7 +19,7 @@ importing ``gratinglab.metrology.gui`` must stay cheap, since that is what
19
19
  raises the friendly "PySide6 is an optional extra" message.
20
20
  ``tests/test_gui_boundary.py`` checks it.
21
21
 
22
- ``tools/make_icon.py`` turns this into a macOS ``.icns``.
22
+ ``tools/apps.py icon`` turns this into a macOS ``.icns``.
23
23
  """
24
24
 
25
25
  from __future__ import annotations