opticalib 2.1.3__tar.gz → 2.3.0__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 (154) hide show
  1. opticalib-2.3.0/CITATION.bib +10 -0
  2. {opticalib-2.1.3 → opticalib-2.3.0}/MANIFEST.in +5 -4
  3. {opticalib-2.1.3/opticalib.egg-info → opticalib-2.3.0}/PKG-INFO +42 -28
  4. {opticalib-2.1.3 → opticalib-2.3.0}/README.md +11 -0
  5. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/__init__.py +11 -15
  6. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/__version__.py +1 -1
  7. opticalib-2.3.0/opticalib/analyzer/__init__.py +13 -0
  8. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/analyzer/image_processing.py +59 -4
  9. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/_types.py +16 -1
  10. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/config.py +53 -2
  11. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/root.py +4 -4
  12. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/alpaoAPI.py +24 -16
  13. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/base_devices.py +25 -10
  14. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/piAPI.py +17 -8
  15. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/deformable_mirrors.py +63 -91
  16. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/interferometer.py +52 -27
  17. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/_misc.py +14 -6
  18. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/flattening.py +32 -1
  19. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/iff_processing.py +1 -3
  20. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/_stitching_algorithm.py +13 -13
  21. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/osutils.py +11 -4
  22. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/reconstructor.py +5 -5
  23. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/__init__.py +19 -3
  24. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/activity.py +47 -14
  25. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/app.py +498 -62
  26. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/kernel.py +146 -17
  27. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/kernel_side.py +88 -0
  28. opticalib-2.3.0/opticalib/gui/plugins.py +304 -0
  29. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/alignment.py +30 -8
  30. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/base.py +51 -14
  31. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/dm_calibration.py +145 -31
  32. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/params.py +64 -18
  33. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/phasing.py +76 -17
  34. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/stitching.py +31 -5
  35. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/timeseries.py +24 -5
  36. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/theme.py +10 -0
  37. opticalib-2.3.0/opticalib/gui/widgets/common.py +298 -0
  38. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/config_editor.py +225 -5
  39. opticalib-2.3.0/opticalib/gui/widgets/connect_dialog.py +503 -0
  40. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/data_browser.py +22 -8
  41. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/device_panel.py +177 -46
  42. opticalib-2.3.0/opticalib/gui/widgets/device_registry.py +1272 -0
  43. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/plot_viewer.py +60 -13
  44. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/workspace.py +6 -2
  45. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/alignment.py +28 -16
  46. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/iff.py +9 -11
  47. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/phasing.py +4 -2
  48. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/__init__.py +2 -0
  49. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/base_fake_adopticadm.py +19 -75
  50. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/base_fake_alpao.py +23 -85
  51. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/base_petalmirror.py +13 -52
  52. opticalib-2.3.0/opticalib/simulator/_API/base_simulated_dm.py +395 -0
  53. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/simdata.py +11 -8
  54. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/__init__.py +2 -0
  55. opticalib-2.3.0/opticalib/simulator/fake_dms.py +281 -0
  56. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/visualization.py +99 -2
  57. {opticalib-2.1.3 → opticalib-2.3.0/opticalib.egg-info}/PKG-INFO +42 -28
  58. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib.egg-info/SOURCES.txt +6 -4
  59. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib.egg-info/requires.txt +22 -3
  60. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib.egg-info/top_level.txt +0 -1
  61. opticalib-2.3.0/pyproject.toml +103 -0
  62. opticalib-2.3.0/setup.cfg +4 -0
  63. {opticalib-2.1.3 → opticalib-2.3.0}/setup_calpy.py +2 -13
  64. opticalib-2.3.0/tests/README.md +110 -0
  65. {opticalib-2.1.3 → opticalib-2.3.0}/tests/conftest.py +3 -3
  66. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_analyzer_images_processing.py +146 -0
  67. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_core_read_config.py +63 -1
  68. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_core_root.py +2 -2
  69. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_devices_deformable_mirrors.py +1 -1
  70. opticalib-2.3.0/tests/test_devices_interferometer.py +93 -0
  71. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_config_editor.py +84 -0
  72. opticalib-2.3.0/tests/test_gui_devices.py +923 -0
  73. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_kernel_bridge.py +46 -0
  74. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_kernel_side.py +86 -0
  75. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_procedures.py +78 -1
  76. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_widgets.py +173 -0
  77. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_setup_calpy.py +13 -11
  78. opticalib-2.3.0/tests/test_simulator_run_cmd_history.py +48 -0
  79. opticalib-2.1.3/opticalib/analyzer/__init__.py +0 -3
  80. opticalib-2.1.3/opticalib/gui/plugins.py +0 -128
  81. opticalib-2.1.3/opticalib/gui/widgets/common.py +0 -137
  82. opticalib-2.1.3/opticalib/gui/widgets/connect_dialog.py +0 -246
  83. opticalib-2.1.3/opticalib/gui/widgets/device_registry.py +0 -642
  84. opticalib-2.1.3/opticalib/simulator/_API/_rbf_gpu.py +0 -367
  85. opticalib-2.1.3/opticalib/simulator/fake_dms.py +0 -846
  86. opticalib-2.1.3/requirements.txt +0 -24
  87. opticalib-2.1.3/setup.cfg +0 -28
  88. opticalib-2.1.3/setup.py +0 -56
  89. opticalib-2.1.3/tests/test_gui_devices.py +0 -448
  90. {opticalib-2.1.3 → opticalib-2.3.0}/LICENSE +0 -0
  91. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/__init_script__/initCalpy.py +0 -0
  92. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/analyzer/signals.py +0 -0
  93. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/analyzer/timeseries.py +0 -0
  94. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/__init__.py +0 -0
  95. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/_configurations/configuration.yaml +0 -0
  96. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/data_classes.py +0 -0
  97. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/decorators.py +0 -0
  98. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/exceptions.py +0 -0
  99. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/core/fitsarray.py +0 -0
  100. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/__init__.py +0 -0
  101. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/i4dAPI.py +0 -0
  102. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/micAPI.py +0 -0
  103. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/_API/splattAPI.py +0 -0
  104. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/__init__.py +0 -0
  105. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/cameras.py +0 -0
  106. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/devices/wfs.py +0 -0
  107. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/__actuator_identification_wip.py +0 -0
  108. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/__init__.py +0 -0
  109. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/__pupil_calibration_wip.py +0 -0
  110. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/iff_preparation.py +0 -0
  111. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/dmutils/slaving.py +0 -0
  112. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/__init__.py +0 -0
  113. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/geometry.py +0 -0
  114. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/logger.py +0 -0
  115. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/modal_decomposer.py +0 -0
  116. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/ground/roi.py +0 -0
  117. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/kernel_backend.py +0 -0
  118. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/procedures/__init__.py +0 -0
  119. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/resources/opticalib.ico +0 -0
  120. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/resources/opticalib.png +0 -0
  121. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/resources/opticalib.svg +0 -0
  122. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/gui/widgets/__init__.py +0 -0
  123. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/__init__.py +0 -0
  124. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/measurements.py +0 -0
  125. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/procedures/stitching.py +0 -0
  126. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/py.typed +0 -0
  127. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/_API/alpao_conf.yaml +0 -0
  128. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/factory.py +0 -0
  129. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib/simulator/fake_interf.py +0 -0
  130. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib.egg-info/dependency_links.txt +0 -0
  131. {opticalib-2.1.3 → opticalib-2.3.0}/opticalib.egg-info/entry_points.txt +0 -0
  132. {opticalib-2.1.3 → opticalib-2.3.0}/tests/__init__.py +0 -0
  133. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_analyzer_signals.py +0 -0
  134. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_analyzer_timeseries.py +0 -0
  135. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_core_dataclass.py +0 -0
  136. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_core_exceptions.py +0 -0
  137. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_core_fits.py +0 -0
  138. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_dmutils_flattening.py +0 -0
  139. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_dmutils_iff_acquisition_preparation.py +0 -0
  140. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_dmutils_iff_module.py +0 -0
  141. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_dmutils_iff_processing.py +0 -0
  142. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_dmutils_slaving.py +0 -0
  143. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_computerec.py +0 -0
  144. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_geometry.py +0 -0
  145. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_logger.py +0 -0
  146. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_modal_decomposer.py +0 -0
  147. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_osutils.py +0 -0
  148. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_ground_roi.py +0 -0
  149. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_activity.py +0 -0
  150. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_gui_app.py +0 -0
  151. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_measurements.py +0 -0
  152. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_simulator_factory_functions.py +0 -0
  153. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_simulator_simdata.py +0 -0
  154. {opticalib-2.1.3 → opticalib-2.3.0}/tests/test_typings.py +0 -0
@@ -0,0 +1,10 @@
1
+ @misc{opticalib,
2
+ doi = {10.20371/INAF/SW/2026_00013},
3
+ url = {https://openscience.inaf.it/?page_id=696},
4
+ author = {{Pietro Ferraiuolo} and {Marco Xompero} and {Runa Briguglio} and {Matteo Menessini}},
5
+ language = {en},
6
+ title = {OPTICALIB: A Python Framework for Adaptive Optics Experimentation},
7
+ publisher = {O.A. Arcetri},
8
+ year = {2025},
9
+ copyright = {MIT License}
10
+ }
@@ -1,8 +1,9 @@
1
1
  include README.md
2
- include requirements.txt
2
+ include LICENSE
3
+ include CITATION.bib
3
4
  include opticalib/core/_configurations/configuration.yaml
4
- include setup_calpy.py
5
- include opticalib/__init_script__/initCalpy.py
6
5
  include opticalib/simulator/_API/alpao_conf.yaml
6
+ include opticalib/__init_script__/initCalpy.py
7
7
  recursive-include opticalib/gui/resources *.svg *.png *.ico
8
- recursive-include labott *.py
8
+ graft tests
9
+ global-exclude __pycache__ *.py[cod]
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opticalib
3
- Version: 2.1.3
3
+ Version: 2.3.0
4
4
  Summary: This package is a python interface for laboratory adaptive optics systems, with a focus on the calibration of deformable mirrors (DMs) and the acquisition of wavefront data using wavefront sensors.
5
- Home-page: https://github.com/ArcetriAdaptiveOptics/opticalib
6
- Author: Adaptive Optics Group - INAF - Osservatorio Astrofisico di Arcetri
7
- Author-email: pietro.ferraiuolo@inaf.it
8
- Maintainer: Pietro Ferraiuolo
9
- License: MIT License
5
+ Author-email: Adaptive Optics Group - INAF - Osservatorio Astrofisico di Arcetri <pietro.ferraiuolo@inaf.it>
6
+ Maintainer-email: Pietro Ferraiuolo <pietro.ferraiuolo@inaf.it>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/ArcetriAdaptiveOptics/opticalib
9
+ Project-URL: Repository, https://github.com/ArcetriAdaptiveOptics/opticalib
10
+ Project-URL: Issues, https://github.com/ArcetriAdaptiveOptics/opticalib/issues
10
11
  Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: Microsoft :: Windows
13
13
  Classifier: Operating System :: POSIX :: Linux
14
14
  Classifier: Operating System :: MacOS
15
- Requires-Python: >=3.10
15
+ Requires-Python: >=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: arte
@@ -24,37 +24,43 @@ Requires-Dist: pipython
24
24
  Requires-Dist: photutils
25
25
  Requires-Dist: scipy
26
26
  Requires-Dist: ruamel.yaml
27
+ Requires-Dist: PyYAML
27
28
  Requires-Dist: Pyro4
28
29
  Requires-Dist: h5py
29
30
  Requires-Dist: jdcal
30
31
  Requires-Dist: tqdm
31
32
  Requires-Dist: matplotlib
32
- Requires-Dist: xupy
33
33
  Requires-Dist: numpy
34
- Requires-Dist: qtconsole
35
- Requires-Dist: PyYAML
36
- Requires-Dist: PySide6-Essentials
37
- Requires-Dist: QtPy
38
- Requires-Dist: ipykernel
39
- Requires-Dist: jupyter_client
40
- Requires-Dist: pyqtgraph
41
- Requires-Dist: qtawesome
42
- Dynamic: author
43
- Dynamic: author-email
44
- Dynamic: classifier
45
- Dynamic: description
46
- Dynamic: description-content-type
47
- Dynamic: home-page
48
- Dynamic: license
34
+ Requires-Dist: rbfgpu
35
+ Requires-Dist: xupy>=2.0
36
+ Requires-Dist: ipython
37
+ Provides-Extra: gui
38
+ Requires-Dist: PySide6-Essentials; extra == "gui"
39
+ Requires-Dist: QtPy; extra == "gui"
40
+ Requires-Dist: qtconsole; extra == "gui"
41
+ Requires-Dist: ipykernel; extra == "gui"
42
+ Requires-Dist: jupyter_client; extra == "gui"
43
+ Requires-Dist: pyqtgraph; extra == "gui"
44
+ Requires-Dist: qtawesome; extra == "gui"
45
+ Requires-Dist: psutil; extra == "gui"
46
+ Provides-Extra: cuda12
47
+ Requires-Dist: xupy[cuda12]>=2.0; extra == "cuda12"
48
+ Provides-Extra: cuda13
49
+ Requires-Dist: xupy[cuda13]>=2.0; extra == "cuda13"
50
+ Provides-Extra: test
51
+ Requires-Dist: pytest; extra == "test"
52
+ Requires-Dist: pytest-cov; extra == "test"
53
+ Provides-Extra: dev
54
+ Requires-Dist: opticalib[gui,test]; extra == "dev"
55
+ Requires-Dist: flake8; extra == "dev"
49
56
  Dynamic: license-file
50
- Dynamic: maintainer
51
- Dynamic: requires-dist
52
- Dynamic: requires-python
53
- Dynamic: summary
54
57
 
55
58
  # OptiCalib : python framework for OPTical CALibration of deformable mirrors and AO experimentation
56
59
 
57
60
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ArcetriAdaptiveOptics/opticalib/tests.yml)
61
+ [![PyPI version](https://img.shields.io/pypi/v/opticalib.svg)](https://pypi.org/project/opticalib/)
62
+ [![Python versions](https://img.shields.io/pypi/pyversions/opticalib.svg)](https://pypi.org/project/opticalib/)
63
+ [![License: MIT](https://img.shields.io/pypi/l/opticalib.svg)](LICENSE)
58
64
 
59
65
  `Adaptive Optics Group, INAF - Osservatorio Astrofisico di Arcetri`
60
66
 
@@ -86,6 +92,14 @@ The latests, stable, version can be installed from pypi:
86
92
  pip install opticalib
87
93
  ```
88
94
 
95
+ The base install contains everything needed to talk to the hardware, run the simulators and the calibration routines, and use the `calpy` shell. Two optional extras add the rest:
96
+
97
+ ```bash
98
+ pip install "opticalib[gui]" # the graphical interface (calpy --gui / OptiCalib)
99
+ pip install "opticalib[cuda13]" # GPU backend (CuPy, via xupy); use cuda12 for CUDA 12.x
100
+ pip install "opticalib[gui,cuda13]" # extras can be combined
101
+ ```
102
+
89
103
  The in-development version can be installed directly from this repository:
90
104
 
91
105
  ```bash
@@ -1,6 +1,9 @@
1
1
  # OptiCalib : python framework for OPTical CALibration of deformable mirrors and AO experimentation
2
2
 
3
3
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ArcetriAdaptiveOptics/opticalib/tests.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/opticalib.svg)](https://pypi.org/project/opticalib/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/opticalib.svg)](https://pypi.org/project/opticalib/)
6
+ [![License: MIT](https://img.shields.io/pypi/l/opticalib.svg)](LICENSE)
4
7
 
5
8
  `Adaptive Optics Group, INAF - Osservatorio Astrofisico di Arcetri`
6
9
 
@@ -32,6 +35,14 @@ The latests, stable, version can be installed from pypi:
32
35
  pip install opticalib
33
36
  ```
34
37
 
38
+ The base install contains everything needed to talk to the hardware, run the simulators and the calibration routines, and use the `calpy` shell. Two optional extras add the rest:
39
+
40
+ ```bash
41
+ pip install "opticalib[gui]" # the graphical interface (calpy --gui / OptiCalib)
42
+ pip install "opticalib[cuda13]" # GPU backend (CuPy, via xupy); use cuda12 for CUDA 12.x
43
+ pip install "opticalib[gui,cuda13]" # extras can be combined
44
+ ```
45
+
35
46
  The in-development version can be installed directly from this repository:
36
47
 
37
48
  ```bash
@@ -5,20 +5,15 @@ OPTICALIB: adaptive OPTIcs package for dm CALIBration
5
5
  Author(s)
6
6
  ---------
7
7
  - Pietro Ferraiuolo : pietro.ferraiuolo@inaf.it
8
+ - Runa Briguglio: runa.briguglio@inaf.it
9
+ - Marco Xompero: marco.xompero@inaf.it
10
+ - Matteo Menessini: matteo.menessini@inaf.it
8
11
 
9
12
  Description
10
13
  -----------
11
14
  OptiCalib is a package for the control of laboratory instrumentations, like
12
15
  Interferometers and Deformable Mirrors. It also provides tools for the
13
16
  analysis of wavefronts and images.
14
-
15
- How to Use:
16
- -----------
17
- ```python
18
- > import opticalib
19
- > interf = opticalib.PhaseCam('193.206.155.218', 8011)
20
- > img = interf.acquire_map()
21
- ```
22
17
  """
23
18
 
24
19
  from __future__ import annotations
@@ -34,13 +29,6 @@ from .core.root import (
34
29
  from .core import config
35
30
  from .core import _types as typings
36
31
  from .core.fitsarray import fits_array
37
- from .devices import *
38
- from .devices.interferometer import _4DInterferometer
39
-
40
- get_camera_settings = _4DInterferometer.get_camera_settings
41
- get_frame_rate = _4DInterferometer.get_frame_rate
42
-
43
- del _4DInterferometer
44
32
 
45
33
  from . import (
46
34
  analyzer,
@@ -51,6 +39,14 @@ from . import (
51
39
  visualization,
52
40
  )
53
41
 
42
+ from .devices.interferometer import _4DInterferometer
43
+
44
+ get_camera_settings = _4DInterferometer.get_camera_settings
45
+ get_frame_rate = _4DInterferometer.get_frame_rate
46
+ del _4DInterferometer
47
+
48
+ from .devices import *
49
+
54
50
  vis = visualization
55
51
 
56
52
  __all__ = [
@@ -9,7 +9,7 @@ Pietro Ferraiuolo
9
9
  __title__ = "opticalib"
10
10
  __long_title__ = "OPTICALIB : python framework for OPTIcal CALIBration of deformable mirrors and adaptive optics experimentation"
11
11
  __description__ = "This package is a python interface for laboratory adaptive optics systems, with a focus on the calibration of deformable mirrors (DMs) and the acquisition of wavefront data using wavefront sensors."
12
- __version__ = "2.1.3"
12
+ __version__ = "2.3.0"
13
13
  __author__ = "Adaptive Optics Group - INAF - Osservatorio Astrofisico di Arcetri"
14
14
  __maintainer__ = "Pietro Ferraiuolo"
15
15
  __author_email__ = "pietro.ferraiuolo@inaf.it"
@@ -0,0 +1,13 @@
1
+ from .image_processing import *
2
+ from .signals import *
3
+ from .timeseries import *
4
+
5
+
6
+ def _get_safe_interf_fullframe():
7
+ from ..devices.interferometer import _4DInterferometer
8
+
9
+ func = _4DInterferometer.into_full_frame
10
+ return func
11
+
12
+
13
+ into_4d_full_frame = _get_safe_interf_fullframe()
@@ -20,6 +20,54 @@ from ..ground import osutils as osu, modal_decomposer as _md
20
20
  from scipy import stats as _stats, fft as _fft, ndimage as _ndimage
21
21
 
22
22
 
23
+ def pad_image_to_shape(
24
+ img: _ot.ImageData,
25
+ shape: tuple[int, int],
26
+ offset: tuple[int, int],
27
+ fill_value: float = 0.0,
28
+ ) -> _ot.ImageData:
29
+ """
30
+ Pads an image to a specified shape with a given offset and fill value.
31
+
32
+ Parameters
33
+ ----------
34
+ img : _ot.ImageData
35
+ The image to be padded.
36
+ shape : tuple[int, int]
37
+ The target shape (height, width) for the padded image.
38
+ offset : tuple[int, int]
39
+ The (y, x) offset at which to place the original image in the padded image.
40
+ fill_value : float, optional
41
+ The value to fill the padded areas with. Default is 0.
42
+
43
+ Returns
44
+ -------
45
+ padded_img : _ot.ImageData
46
+ The padded image.
47
+ """
48
+ y, x, Y, X, h, w = [*offset, *shape, *img.shape]
49
+ if any(p < 0 for p in [y, x, Y - h - y, X - w - x]):
50
+ raise ValueError("Invalid padding dimensions")
51
+ pad_width = ((y, Y - h - y), (x, X - w - x))
52
+ pimg = _np.pad(
53
+ img.data,
54
+ pad_width=pad_width,
55
+ mode='constant',
56
+ constant_values=fill_value
57
+ )
58
+ mask = _np.pad(
59
+ _np.ma.getmaskarray(img),
60
+ pad_width=pad_width,
61
+ mode='constant',
62
+ constant_values=True
63
+ )
64
+ if hasattr(img, 'header'):
65
+ header = img.header
66
+ else:
67
+ header = None
68
+ return _fa.fits_array(pimg, mask=mask, header=header)
69
+
70
+
23
71
  def frame(idx: int, mylist: list[_ot.ImageData] | _ot.CubeData) -> _ot.ImageData:
24
72
  """
25
73
  Returns a single frame from a list of files or from a cube.
@@ -225,7 +273,7 @@ def push_pull_reduction_algorithm(
225
273
  # Template weights computation
226
274
  w = _xp.asarray(
227
275
  template.astype(_np.result_type(template, imagelist[0].data), copy=True),
228
- dtype=_xp.float,
276
+ dtype=_ot.compute_float(),
229
277
  )
230
278
  if n_images > 2:
231
279
  w[1:-1] *= 2.0
@@ -233,9 +281,9 @@ def push_pull_reduction_algorithm(
233
281
  master_mask = _np.logical_or.reduce([ima.mask for ima in imagelist])
234
282
  # Compute weighted sum over realizations on raw data
235
283
  stack = _xp.stack(
236
- [_xp.asarray(ima.data, dtype=_xp.float) for ima in imagelist],
284
+ [_xp.asarray(ima.data, dtype=_ot.compute_float()) for ima in imagelist],
237
285
  axis=0,
238
- dtype=_xp.float,
286
+ dtype=_ot.compute_float(),
239
287
  ) # (n, H, W)
240
288
  image = _xp.asnumpy(_xp.tensordot(w, stack, axes=(0, 0))) # (H, W)
241
289
 
@@ -269,13 +317,18 @@ def create_cube(fl_or_il: list[str], register: bool = False) -> _ot.CubeData:
269
317
  cube : ndarray
270
318
  Data cube containing the images/frames stacked, with shape (npx, npy, nframes).
271
319
  """
320
+ memmap = True
272
321
  # check it is a list
273
322
  if not isinstance(fl_or_il, list):
274
323
  raise TypeError("filelist must be a list of strings or images")
275
324
 
276
325
  # check if it is composed of file paths to load
277
326
  if all([isinstance(item, str) for item in fl_or_il]):
278
- fl_or_il = [osu.read_phasemap(f) for f in fl_or_il]
327
+ N = len(fl_or_il) # Number of files
328
+ if N > 1000:
329
+ memmap = False
330
+ print("Disabling memory mapping for large file list: N =", N)
331
+ fl_or_il = [osu.read_phasemap(f, memmap=memmap) for f in fl_or_il]
279
332
 
280
333
  # Is the list now full of images?
281
334
  if not any(
@@ -860,6 +913,8 @@ def rebin2_d_array(
860
913
 
861
914
 
862
915
  __all__ = [
916
+ "pad_image_to_shape",
917
+ # "into_4d_full_frame",
863
918
  "frame",
864
919
  "unwrap_piston",
865
920
  "unwrap_image",
@@ -162,7 +162,7 @@ class _DMProtocol(Protocol):
162
162
  def set_shape(self, cmd: MatrixLike, differential: bool) -> None: ...
163
163
  def get_shape(self) -> ArrayLike: ...
164
164
  def upload_cmd_history(
165
- self, cmdhist: MatrixLike, *, slave: bool | str = False
165
+ self, cmdhist: MatrixLike, *, slaving: bool | str = False
166
166
  ) -> None: ...
167
167
  def run_cmd_history(
168
168
  self,
@@ -453,3 +453,18 @@ def get_device_type(device: object) -> str:
453
453
  elif isinstance_(device, "CameraDevice"):
454
454
  return "camera"
455
455
  return device._name
456
+
457
+
458
+ def compute_float():
459
+ """
460
+ Floating dtype for numerical work on the active xupy backend.
461
+
462
+ ``float32`` on GPU (CuPy) for speed, ``float64`` on CPU (NumPy). This is
463
+ what the removed ``xupy.float`` alias used to resolve to; it is evaluated
464
+ at call time, so it follows ``xupy.use_cpu()`` / ``xupy.use_gpu()``.
465
+ """
466
+ import xupy as xp
467
+
468
+ dtype = xp.float32 if xp.on_gpu else xp.float64
469
+ del xp
470
+ return dtype
@@ -15,6 +15,7 @@ Most accessors also accept an explicit ``bpath`` to point at a specific
15
15
 
16
16
  import os as _os
17
17
  import re as _re
18
+ from collections.abc import Mapping as _Mapping
18
19
  from ast import literal_eval as _literal_eval
19
20
 
20
21
  import numpy as _np
@@ -276,6 +277,52 @@ def get_section_config(section: str, sub_section: str | None = None) -> dict[str
276
277
  return _get_section_config(section=section, sub_section=sub_section, path=_cfile)
277
278
 
278
279
 
280
+ class LazySection(_Mapping):
281
+ """
282
+ A section of ``configuration.yaml``, read when it is used.
283
+
284
+ Modules that need a section at import time use it instead of reading the
285
+ file immediately, so that an experiment that does not define the
286
+ section (or sets it to ``null``) can still import opticalib. A missing
287
+ section reads as empty; ``section[key]`` then raises a ``KeyError``
288
+ naming the section. The file is read on every access, so the values
289
+ follow :func:`opticalib.set_configuration_file`.
290
+
291
+ Parameters
292
+ ----------
293
+ section : str
294
+ Top-level section name.
295
+ """
296
+
297
+ def __init__(self, section: str) -> None:
298
+ """Remember the section; nothing is read yet."""
299
+ self._section = section
300
+
301
+ def _load(self) -> dict[str, _Any]:
302
+ try:
303
+ data = get_section_config(self._section)
304
+ except KeyError:
305
+ data = None
306
+ return dict(data) if isinstance(data, dict) else {}
307
+
308
+ def __getitem__(self, key: str) -> _Any:
309
+ data = self._load()
310
+ if key not in data:
311
+ raise KeyError(
312
+ f"`{key}` not found in the `{self._section}` section of the configuration file"
313
+ )
314
+ return data[key]
315
+
316
+ def __iter__(self):
317
+ return iter(self._load())
318
+
319
+ def __len__(self) -> int:
320
+ return len(self._load())
321
+
322
+ def __repr__(self) -> str:
323
+ return f"LazySection({self._section!r})"
324
+
325
+
279
326
  def get_device_config(device_type: str, device_name: str | None = None):
280
327
  """
281
328
  Retrieves the device configuration from the YAML configuration file.
@@ -558,8 +605,8 @@ def get_cmd_delay(bpath: str = _cfold):
558
605
  sub_section="DM",
559
606
  path=bpath,
560
607
  )
561
- if "sequentialDelay" in dm_config:
562
- cmdDelay = float(dm_config["sequentialDelay"])
608
+ if "sequential_delay" in dm_config:
609
+ cmdDelay = float(dm_config["sequential_delay"])
563
610
  elif "delay" in dm_config:
564
611
  cmdDelay = float(dm_config["delay"])
565
612
  elif "triggeredMode" in dm_config and "cmdDelay" in dm_config["triggeredMode"]:
@@ -696,6 +743,10 @@ def get_alignment_config():
696
743
  The alignment configuration as a class, for backwards compatibility.
697
744
  """
698
745
  config = get_section_config("SYSTEM.ALIGNMENT")
746
+ if not isinstance(config, dict):
747
+ raise KeyError(
748
+ "The `SYSTEM.ALIGNMENT` section of the configuration file is empty."
749
+ )
699
750
  config["slices"] = [slice(item["start"], item["stop"]) for item in config["slices"]]
700
751
 
701
752
  class AlignmentConfig:
@@ -351,7 +351,7 @@ class ConfSettingReader4D:
351
351
  defined object:
352
352
 
353
353
  >>> cr = ConfSettingReader4D(file_path)
354
- >>> cr.get_image_widht_in_pixels()
354
+ >>> cr.get_image_width_in_pixels()
355
355
  2000
356
356
  >>> cr.get_image_height_in_pixels()
357
357
  2000
@@ -382,13 +382,13 @@ class ConfSettingReader4D:
382
382
  frame_rate = self.config.get(self.camera_section, "FrameRate")
383
383
  return float(frame_rate)
384
384
 
385
- def get_image_widht_in_pixels(self):
385
+ def get_image_width_in_pixels(self):
386
386
  """
387
- Returns the image widht in pixel scale
387
+ Returns the image width in pixel scale
388
388
 
389
389
  Returns
390
390
  -------
391
- image_wight_in_pixels : int
391
+ image_width_in_pixels : int
392
392
  Image pixel width.
393
393
  """
394
394
  image_width_in_pixels = self.config.get(
@@ -5,6 +5,7 @@ from opticalib.core import _types as _t
5
5
 
6
6
  import weakref
7
7
 
8
+
8
9
  def _shutdown_dm(dm, reset: bool, logger, name: str) -> None:
9
10
  """Stop (and optionally Reset) a raw SDK handle. Module-level so it
10
11
  holds no reference to the wrapper object."""
@@ -112,12 +113,15 @@ class BaseAlpaoMirror:
112
113
  self._resolve_init(sdk_params, plico_params)
113
114
  self._is_plico = bool(plico_params[0])
114
115
 
115
- self.n_acts = int(
116
- self._plico_dm.get_number_of_actuators()
117
- ) if self._is_plico else int(self._sdk_dm.Get("NbOfActuator"))
118
-
116
+ self.n_acts = (
117
+ int(self._plico_dm.get_number_of_actuators())
118
+ if self._is_plico
119
+ else int(self._sdk_dm.Get("NbOfActuator"))
120
+ )
121
+
119
122
  if self.n_acts != int(nacts):
120
123
  import warnings
124
+
121
125
  warnings.warn(
122
126
  f"Number of actuators reported by the SDK ({self.n_acts}) "
123
127
  f"does not match the called number ({nacts}). Verify your BAX files",
@@ -131,7 +135,7 @@ class BaseAlpaoMirror:
131
135
  self._name
132
136
  ].get("diameter", None)
133
137
  self.mirrorModes = None
134
- self.cmdHistory = None
138
+ self._cmd_history = None
135
139
  self.refAct = None
136
140
  self.ff = None
137
141
 
@@ -194,12 +198,14 @@ class BaseAlpaoMirror:
194
198
  if not self._is_plico:
195
199
  return int(self._sdk_dm.Get("VersionInfo"))
196
200
  else:
197
- raise AttributeError("Version information is not available for PLICO devices.")
201
+ raise AttributeError(
202
+ "Version information is not available for PLICO devices."
203
+ )
198
204
 
199
205
  def close(self) -> None:
200
206
  """
201
207
  Stop the DM (and Reset it if ``reset_on_close``) and release the hardware
202
- resources.
208
+ resources.
203
209
 
204
210
  Should be called when the DM object is no longer needed to
205
211
  ensure a clean shutdown of the Alpao SDK connection.
@@ -311,15 +317,13 @@ class BaseAlpaoMirror:
311
317
 
312
318
  def _resolve_init(
313
319
  self,
314
- sdk_params: tuple[str|None, str|None, str|None],
315
- plico_params: tuple[str|None, str|None, int|None]
320
+ sdk_params: tuple[str | None, str | None, str | None],
321
+ plico_params: tuple[str | None, str | None, int | None],
316
322
  ):
317
323
  sn, sdk_fold, acfg_p = sdk_params
318
324
  plico, plico_ip, plico_port = plico_params
319
325
 
320
- config = get_section_config(
321
- "DEVICES", "DEFORMABLE.MIRRORS"
322
- ).get(self._name, {})
326
+ config = get_section_config("DEVICES", "DEFORMABLE.MIRRORS").get(self._name, {})
323
327
 
324
328
  if plico:
325
329
  self._plico_ip = config.get("plico_ip", plico_ip)
@@ -351,12 +355,12 @@ class BaseAlpaoMirror:
351
355
  The serial number is resolved in the following priority order:
352
356
 
353
357
  1. The *serial_number* argument, if not ``None``.
354
- 2. The ``serialNumber`` key in the device configuration file
358
+ 2. The ``serial_number`` key in the device configuration file
355
359
  (looked up by *nacts*).
356
360
 
357
361
  When *nacts* is provided the device configuration block
358
362
  ``Alpao{nacts}`` is read from the configuration file, allowing
359
- ``serialNumber``, ``sdk_folder_path``, and ``acfg_path`` to be
363
+ ``serial_number``, ``sdk_folder_path``, and ``acfg_path`` to be
360
364
  set there instead of being passed as arguments. When only
361
365
  *serial_number* is given the configuration lookup is skipped and
362
366
  the caller is responsible for having ``ACECFG`` set in the
@@ -396,9 +400,13 @@ class BaseAlpaoMirror:
396
400
  os.environ["ACECFG"] = self.acfg_path
397
401
 
398
402
  try:
399
- sdk_path = self.sdk_folder_path or os.path.join(CONFIGURATION_FOLDER, "alpao_sdk")
403
+ sdk_path = self.sdk_folder_path or os.path.join(
404
+ CONFIGURATION_FOLDER, "alpao_sdk"
405
+ )
400
406
 
401
- if self.sdk_folder_path is not None and not os.path.exists(self.sdk_folder_path):
407
+ if self.sdk_folder_path is not None and not os.path.exists(
408
+ self.sdk_folder_path
409
+ ):
402
410
  sdk_path = os.path.join(CONFIGURATION_FOLDER, self.sdk_folder_path)
403
411
 
404
412
  if not os.path.exists(sdk_path):
@@ -36,6 +36,26 @@ class BaseDeformableMirror(ABC):
36
36
  Base class for all deformable mirror devices.
37
37
  """
38
38
 
39
+ @property
40
+ def slave_ids(self):
41
+ return self._slaveIds
42
+
43
+ @property
44
+ def border_ids(self):
45
+ return self._borderIds
46
+
47
+ @property
48
+ def cmd_history(self) -> _ot.Optional[_ot.MatrixLike]:
49
+ """
50
+ Get the command history of the simulated deformable mirror (DM).
51
+
52
+ Returns
53
+ -------
54
+ cmd_history : _ot.Optional[_ot.MatrixLike]
55
+ The command history of the DM, or None if no history has been uploaded.
56
+ """
57
+ return self._cmd_history
58
+
39
59
  @abstractmethod
40
60
  def set_shape(self, cmd):
41
61
  """
@@ -97,24 +117,19 @@ class BaseDeformableMirror(ABC):
97
117
  return None
98
118
  return "minimum-rms" if (s and b) else "zero-force"
99
119
 
100
- def _apply_slaving(self, cmd: _ot.ArrayLike, slave: bool | str) -> _ot.ArrayLike:
120
+ def _apply_slaving(
121
+ self, cmd: _ot.ArrayLike | _ot.MatrixLike, slave: bool | str
122
+ ) -> _ot.ArrayLike | _ot.MatrixLike:
101
123
  """
102
124
  Apply actuator slaving to a command when requested.
103
125
  """
104
126
  method = self._get_slaving_method(slave)
105
127
  if method is None:
106
128
  return cmd
129
+ if _ot.isinstance_(cmd, "MatrixLike"):
130
+ return compute_slaved_command_matrix(self, cmdmat=cmd, method=slave)
107
131
  return compute_slave_cmd(self, cmd, method=method)
108
132
 
109
- def _slave_cmdmat(self, cmdmat: _ot.ArrayLike, slave: bool | str) -> _ot.MatrixLike:
110
- """
111
- Apply actuator slaving to a command matrix when requested.
112
- """
113
- method = self._get_slaving_method(slave)
114
- if method is None:
115
- return cmdmat
116
- return compute_slaved_command_matrix(self, cmdmat, method=method)
117
-
118
133
 
119
134
  class BaseCamera(ABC):
120
135