opticalib 2.2.0__tar.gz → 2.3.1__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 (148) hide show
  1. {opticalib-2.2.0/opticalib.egg-info → opticalib-2.3.1}/PKG-INFO +6 -2
  2. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/__version__.py +1 -1
  3. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/analyzer/image_processing.py +27 -6
  4. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/config.py +51 -0
  5. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/alpaoAPI.py +2 -2
  6. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/base_devices.py +4 -2
  7. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/piAPI.py +12 -3
  8. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/deformable_mirrors.py +7 -1
  9. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/_misc.py +14 -6
  10. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/osutils.py +11 -4
  11. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/__init__.py +10 -1
  12. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/app.py +191 -23
  13. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/kernel.py +15 -3
  14. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/kernel_side.py +88 -0
  15. opticalib-2.3.1/opticalib/gui/plugins.py +304 -0
  16. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/theme.py +10 -0
  17. opticalib-2.3.1/opticalib/gui/widgets/common.py +298 -0
  18. opticalib-2.3.1/opticalib/gui/widgets/connect_dialog.py +503 -0
  19. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/device_panel.py +128 -27
  20. opticalib-2.3.1/opticalib/gui/widgets/device_registry.py +1272 -0
  21. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/alignment.py +28 -16
  22. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/phasing.py +4 -2
  23. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/base_fake_alpao.py +2 -7
  24. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/base_petalmirror.py +2 -0
  25. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/base_simulated_dm.py +64 -47
  26. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/simdata.py +11 -8
  27. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/fake_dms.py +4 -1
  28. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/visualization.py +18 -9
  29. {opticalib-2.2.0 → opticalib-2.3.1/opticalib.egg-info}/PKG-INFO +6 -2
  30. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib.egg-info/SOURCES.txt +1 -0
  31. {opticalib-2.2.0 → opticalib-2.3.1}/pyproject.toml +5 -1
  32. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_analyzer_images_processing.py +127 -5
  33. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_core_read_config.py +62 -0
  34. opticalib-2.3.1/tests/test_gui_devices.py +923 -0
  35. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_kernel_bridge.py +15 -0
  36. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_kernel_side.py +63 -0
  37. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_procedures.py +78 -1
  38. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_widgets.py +126 -0
  39. opticalib-2.3.1/tests/test_simulator_run_cmd_history.py +48 -0
  40. opticalib-2.2.0/opticalib/gui/plugins.py +0 -130
  41. opticalib-2.2.0/opticalib/gui/widgets/common.py +0 -144
  42. opticalib-2.2.0/opticalib/gui/widgets/connect_dialog.py +0 -262
  43. opticalib-2.2.0/opticalib/gui/widgets/device_registry.py +0 -735
  44. opticalib-2.2.0/tests/test_gui_devices.py +0 -448
  45. {opticalib-2.2.0 → opticalib-2.3.1}/CITATION.bib +0 -0
  46. {opticalib-2.2.0 → opticalib-2.3.1}/LICENSE +0 -0
  47. {opticalib-2.2.0 → opticalib-2.3.1}/MANIFEST.in +0 -0
  48. {opticalib-2.2.0 → opticalib-2.3.1}/README.md +0 -0
  49. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/__init__.py +0 -0
  50. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/__init_script__/initCalpy.py +0 -0
  51. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/analyzer/__init__.py +0 -0
  52. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/analyzer/signals.py +0 -0
  53. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/analyzer/timeseries.py +0 -0
  54. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/__init__.py +0 -0
  55. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/_configurations/configuration.yaml +0 -0
  56. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/_types.py +0 -0
  57. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/data_classes.py +0 -0
  58. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/decorators.py +0 -0
  59. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/exceptions.py +0 -0
  60. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/fitsarray.py +0 -0
  61. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/core/root.py +0 -0
  62. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/__init__.py +0 -0
  63. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/i4dAPI.py +0 -0
  64. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/micAPI.py +0 -0
  65. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/_API/splattAPI.py +0 -0
  66. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/__init__.py +0 -0
  67. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/cameras.py +0 -0
  68. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/interferometer.py +0 -0
  69. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/devices/wfs.py +0 -0
  70. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/__actuator_identification_wip.py +0 -0
  71. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/__init__.py +0 -0
  72. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/__pupil_calibration_wip.py +0 -0
  73. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/flattening.py +0 -0
  74. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/iff_preparation.py +0 -0
  75. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/iff_processing.py +0 -0
  76. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/dmutils/slaving.py +0 -0
  77. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/__init__.py +0 -0
  78. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/_stitching_algorithm.py +0 -0
  79. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/geometry.py +0 -0
  80. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/logger.py +0 -0
  81. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/modal_decomposer.py +0 -0
  82. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/reconstructor.py +0 -0
  83. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/ground/roi.py +0 -0
  84. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/activity.py +0 -0
  85. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/kernel_backend.py +0 -0
  86. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/__init__.py +0 -0
  87. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/alignment.py +0 -0
  88. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/base.py +0 -0
  89. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/dm_calibration.py +0 -0
  90. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/params.py +0 -0
  91. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/phasing.py +0 -0
  92. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/stitching.py +0 -0
  93. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/procedures/timeseries.py +0 -0
  94. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/resources/opticalib.ico +0 -0
  95. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/resources/opticalib.png +0 -0
  96. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/resources/opticalib.svg +0 -0
  97. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/__init__.py +0 -0
  98. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/config_editor.py +0 -0
  99. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/data_browser.py +0 -0
  100. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/plot_viewer.py +0 -0
  101. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/gui/widgets/workspace.py +0 -0
  102. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/__init__.py +0 -0
  103. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/iff.py +0 -0
  104. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/measurements.py +0 -0
  105. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/procedures/stitching.py +0 -0
  106. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/py.typed +0 -0
  107. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/__init__.py +0 -0
  108. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/alpao_conf.yaml +0 -0
  109. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/_API/base_fake_adopticadm.py +0 -0
  110. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/__init__.py +0 -0
  111. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/factory.py +0 -0
  112. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib/simulator/fake_interf.py +0 -0
  113. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib.egg-info/dependency_links.txt +0 -0
  114. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib.egg-info/entry_points.txt +0 -0
  115. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib.egg-info/requires.txt +0 -0
  116. {opticalib-2.2.0 → opticalib-2.3.1}/opticalib.egg-info/top_level.txt +0 -0
  117. {opticalib-2.2.0 → opticalib-2.3.1}/setup.cfg +0 -0
  118. {opticalib-2.2.0 → opticalib-2.3.1}/setup_calpy.py +0 -0
  119. {opticalib-2.2.0 → opticalib-2.3.1}/tests/README.md +0 -0
  120. {opticalib-2.2.0 → opticalib-2.3.1}/tests/__init__.py +0 -0
  121. {opticalib-2.2.0 → opticalib-2.3.1}/tests/conftest.py +0 -0
  122. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_analyzer_signals.py +0 -0
  123. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_analyzer_timeseries.py +0 -0
  124. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_core_dataclass.py +0 -0
  125. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_core_exceptions.py +0 -0
  126. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_core_fits.py +0 -0
  127. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_core_root.py +0 -0
  128. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_devices_deformable_mirrors.py +0 -0
  129. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_devices_interferometer.py +0 -0
  130. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_dmutils_flattening.py +0 -0
  131. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_dmutils_iff_acquisition_preparation.py +0 -0
  132. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_dmutils_iff_module.py +0 -0
  133. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_dmutils_iff_processing.py +0 -0
  134. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_dmutils_slaving.py +0 -0
  135. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_computerec.py +0 -0
  136. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_geometry.py +0 -0
  137. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_logger.py +0 -0
  138. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_modal_decomposer.py +0 -0
  139. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_osutils.py +0 -0
  140. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_ground_roi.py +0 -0
  141. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_activity.py +0 -0
  142. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_app.py +0 -0
  143. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_gui_config_editor.py +0 -0
  144. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_measurements.py +0 -0
  145. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_setup_calpy.py +0 -0
  146. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_simulator_factory_functions.py +0 -0
  147. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_simulator_simdata.py +0 -0
  148. {opticalib-2.2.0 → opticalib-2.3.1}/tests/test_typings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opticalib
3
- Version: 2.2.0
3
+ Version: 2.3.1
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
5
  Author-email: Adaptive Optics Group - INAF - Osservatorio Astrofisico di Arcetri <pietro.ferraiuolo@inaf.it>
6
6
  Maintainer-email: Pietro Ferraiuolo <pietro.ferraiuolo@inaf.it>
@@ -9,10 +9,14 @@ Project-URL: Homepage, https://github.com/ArcetriAdaptiveOptics/opticalib
9
9
  Project-URL: Repository, https://github.com/ArcetriAdaptiveOptics/opticalib
10
10
  Project-URL: Issues, https://github.com/ArcetriAdaptiveOptics/opticalib/issues
11
11
  Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
12
16
  Classifier: Operating System :: Microsoft :: Windows
13
17
  Classifier: Operating System :: POSIX :: Linux
14
18
  Classifier: Operating System :: MacOS
15
- Requires-Python: >=3.10
19
+ Requires-Python: >=3.11
16
20
  Description-Content-Type: text/markdown
17
21
  License-File: LICENSE
18
22
  Requires-Dist: arte
@@ -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.2.0"
12
+ __version__ = "2.3.1"
13
13
  __author__ = "Adaptive Optics Group - INAF - Osservatorio Astrofisico di Arcetri"
14
14
  __maintainer__ = "Pietro Ferraiuolo"
15
15
  __author_email__ = "pietro.ferraiuolo@inaf.it"
@@ -45,11 +45,27 @@ def pad_image_to_shape(
45
45
  padded_img : _ot.ImageData
46
46
  The padded image.
47
47
  """
48
- padded_img = _np.full(shape, fill_value)
49
- y, x = offset
50
- h, w = _np.shape(img)
51
- padded_img[y : y + h, x : x + w] = img.data
52
- return padded_img
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)
53
69
 
54
70
 
55
71
  def frame(idx: int, mylist: list[_ot.ImageData] | _ot.CubeData) -> _ot.ImageData:
@@ -301,13 +317,18 @@ def create_cube(fl_or_il: list[str], register: bool = False) -> _ot.CubeData:
301
317
  cube : ndarray
302
318
  Data cube containing the images/frames stacked, with shape (npx, npy, nframes).
303
319
  """
320
+ memmap = True
304
321
  # check it is a list
305
322
  if not isinstance(fl_or_il, list):
306
323
  raise TypeError("filelist must be a list of strings or images")
307
324
 
308
325
  # check if it is composed of file paths to load
309
326
  if all([isinstance(item, str) for item in fl_or_il]):
310
- 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]
311
332
 
312
333
  # Is the list now full of images?
313
334
  if not any(
@@ -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.
@@ -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:
@@ -355,12 +355,12 @@ class BaseAlpaoMirror:
355
355
  The serial number is resolved in the following priority order:
356
356
 
357
357
  1. The *serial_number* argument, if not ``None``.
358
- 2. The ``serialNumber`` key in the device configuration file
358
+ 2. The ``serial_number`` key in the device configuration file
359
359
  (looked up by *nacts*).
360
360
 
361
361
  When *nacts* is provided the device configuration block
362
362
  ``Alpao{nacts}`` is read from the configuration file, allowing
363
- ``serialNumber``, ``sdk_folder_path``, and ``acfg_path`` to be
363
+ ``serial_number``, ``sdk_folder_path``, and ``acfg_path`` to be
364
364
  set there instead of being passed as arguments. When only
365
365
  *serial_number* is given the configuration lookup is skipped and
366
366
  the caller is responsible for having ``ACECFG`` set in the
@@ -43,7 +43,7 @@ class BaseDeformableMirror(ABC):
43
43
  @property
44
44
  def border_ids(self):
45
45
  return self._borderIds
46
-
46
+
47
47
  @property
48
48
  def cmd_history(self) -> _ot.Optional[_ot.MatrixLike]:
49
49
  """
@@ -117,7 +117,9 @@ class BaseDeformableMirror(ABC):
117
117
  return None
118
118
  return "minimum-rms" if (s and b) else "zero-force"
119
119
 
120
- def _apply_slaving(self, cmd: _ot.ArrayLike|_ot.MatrixLike, slave: bool | str) -> _ot.ArrayLike|_ot.MatrixLike:
120
+ def _apply_slaving(
121
+ self, cmd: _ot.ArrayLike | _ot.MatrixLike, slave: bool | str
122
+ ) -> _ot.ArrayLike | _ot.MatrixLike:
121
123
  """
122
124
  Apply actuator slaving to a command when requested.
123
125
  """
@@ -7,8 +7,8 @@ from opticalib.core.exceptions import CommandError
7
7
 
8
8
  _CMDLIMITS = { # temporary limits to overcome the hardware connection refusal
9
9
  0: [0, 12],
10
- 1: [-200, 200],
11
- 2: [-200, 200],
10
+ 1: [-230, 230],
11
+ 2: [-230, 230],
12
12
  }
13
13
 
14
14
 
@@ -199,7 +199,7 @@ class BasePetalMirror:
199
199
  self._had_error = True
200
200
  raise RuntimeError("Failed to send mirror command") from err
201
201
 
202
- def _check_cmd_integrity(self, cmd: _ot.ArrayLike) -> None:
202
+ def _check_cmd_integrity(self, cmd: _ot.ArrayLike) -> bool:
203
203
  """
204
204
  Check the integrity of the command array.
205
205
 
@@ -217,6 +217,7 @@ class BasePetalMirror:
217
217
  for ax, c in enumerate(cmd[k * 3 : k * 3 + 3]):
218
218
  if not (_CMDLIMITS[ax][0] <= c <= _CMDLIMITS[ax][1]):
219
219
  raise CommandError(f"Command for actuator {ax} out of bounds: {c}")
220
+ return True
220
221
 
221
222
  def _morning_routine(self) -> None:
222
223
  """
@@ -227,6 +228,14 @@ class BasePetalMirror:
227
228
  import time
228
229
 
229
230
  pos = self._read_act_position()
231
+ if not self._check_cmd_integrity(pos):
232
+ self._logger.warning(
233
+ "Initial actuator positions are not within command limits. "
234
+ "Setting to mid-range.")
235
+ midpoint = lambda v: (v[0] + v[1]) / 2
236
+ pos = np.asarray(
237
+ [midpoint(_CMDLIMITS[ax]) for ax in range(3)] * self.n_segments
238
+ )
230
239
  try:
231
240
  if not self._had_morning_routine:
232
241
  self._logger.info(f"Warming up the segments piezos.")
@@ -30,6 +30,7 @@ from opticalib.ground.logger import SystemLogger as _SL
30
30
 
31
31
  from ._API.piAPI import BasePetalMirror
32
32
 
33
+
33
34
  class PetalMirror(BasePetalMirror, BaseDeformableMirror):
34
35
  """
35
36
  Petal Deformable Mirror interface.
@@ -186,6 +187,7 @@ class PetalMirror(BasePetalMirror, BaseDeformableMirror):
186
187
 
187
188
  from ._API.micAPI import BaseAdOpticaDm
188
189
 
190
+
189
191
  class AdOpticaDm(BaseAdOpticaDm, BaseDeformableMirror):
190
192
  """
191
193
  AdOptica Deformable Mirror interface.
@@ -443,6 +445,7 @@ class AdOpticaDm(BaseAdOpticaDm, BaseDeformableMirror):
443
445
  def __repr__(self):
444
446
  return f"{self._name}(n_segments={self.n_segments}, n_acts_per_segment={self.n_acts_per_segment})"
445
447
 
448
+
446
449
  class DP(AdOpticaDm):
447
450
  """
448
451
  Deformable Mirror interface for the Deformable Platform (DP) of the ELT.
@@ -586,6 +589,7 @@ class DP(AdOpticaDm):
586
589
  # Store in both the yielded dict and class attribute
587
590
  self.bufferData = result.copy()
588
591
 
592
+
589
593
  class M4AU(AdOpticaDm):
590
594
  """
591
595
  Deformable Mirror interface for the M4 Auxiliary Unit (M4AU) of the ELT.
@@ -611,6 +615,7 @@ class M4AU(AdOpticaDm):
611
615
 
612
616
  from ._API.alpaoAPI import BaseAlpaoMirror
613
617
 
618
+
614
619
  class AlpaoDm(BaseAlpaoMirror, BaseDeformableMirror):
615
620
  """
616
621
  Alpao Deformable Mirror interface.
@@ -757,7 +762,7 @@ class AlpaoDm(BaseAlpaoMirror, BaseDeformableMirror):
757
762
  if not _ot.isinstance_(tcmdhist, "MatrixLike"):
758
763
  raise _oe.MatrixError(
759
764
  f"Expecting a 2D Matrix of shape (nacts, nmodes), got instead: {tcmdhist.shape}"
760
- )
765
+ )
761
766
  tcmdhist = self._apply_slaving(cmd=tcmdhist, slave=slaving)
762
767
  self._cmd_history = tcmdhist
763
768
 
@@ -889,6 +894,7 @@ class AlpaoDm(BaseAlpaoMirror, BaseDeformableMirror):
889
894
 
890
895
  from ._API.splattAPI import SPLATTEngine
891
896
 
897
+
892
898
  class SplattDm(BaseDeformableMirror):
893
899
  """
894
900
  SPLATT deformable mirror interface.
@@ -4,9 +4,10 @@ from ..core import _types as _ot
4
4
 
5
5
 
6
6
  def make_modal_base(
7
- rm: _ot.MatrixLike,
8
7
  modes: int | list[int],
9
8
  mask: _ot.MaskData,
9
+ im: _ot.MatrixLike|None = None,
10
+ rm: _ot.MatrixLike|None = None,
10
11
  basis: str = "zernike",
11
12
  ) -> _ot.MatrixLike:
12
13
  """
@@ -14,8 +15,10 @@ def make_modal_base(
14
15
 
15
16
  Parameters
16
17
  ----------
17
- RM : MatrixLike
18
- The reconstruction matrix.
18
+ im : MatrixLike
19
+ The Interaction Matrix (nacts, npix).
20
+ rm : MatrixLike
21
+ The reconstruction matrix (npix, nacts).
19
22
  modes : int or list of int
20
23
  The number of modes to compute, or the list of mode indices to compute.
21
24
  mask : MaskData
@@ -30,7 +33,7 @@ def make_modal_base(
30
33
 
31
34
  Returns
32
35
  -------
33
- array_like
36
+ m2c : MatrixLike
34
37
  The modal base.
35
38
 
36
39
  """
@@ -64,8 +67,13 @@ def make_modal_base(
64
67
  masked_data = surf[~mask]
65
68
  mat[i, :] = masked_data
66
69
 
67
- MB = (mat @ rm).T
68
- return MB
70
+ if rm is not None:
71
+ m2c = (mat @ rm).T
72
+ elif im is not None:
73
+ m2c = (mat @ im.T).T
74
+ else:
75
+ raise ValueError("Either `rm` or `im` must be provided.")
76
+ return m2c
69
77
 
70
78
 
71
79
  __all__ = ["make_modal_base"]
@@ -457,7 +457,7 @@ def load_cube_from_filelist(
457
457
  return cube
458
458
 
459
459
 
460
- def read_phasemap(file_path: str) -> _ot.ImageData:
460
+ def read_phasemap(file_path: str, **kwargs: dict[str, _ot.Any]) -> _ot.ImageData:
461
461
  """
462
462
  Function to read interferometric data, in the three possible formats
463
463
  (FITS, 4D, H5)
@@ -474,13 +474,15 @@ def read_phasemap(file_path: str) -> _ot.ImageData:
474
474
  """
475
475
  ext = file_path.split(".")[-1]
476
476
  if ext in ["fits", "4Ds"]:
477
- image = load_fits(file_path)
477
+ image = load_fits(file_path, **kwargs)
478
478
  elif ext in ["4D", "h5"]:
479
479
  image = _InterferometerConverter.from_phase_cam6110(file_path)
480
480
  return image
481
481
 
482
482
 
483
- def load_fits(filepath: str, on_gpu: bool = False) -> _ot.FitsData | list[_ot.FitsData]:
483
+ def load_fits(
484
+ filepath: str, on_gpu: bool = False, **kwargs: dict[str, _ot.Any]
485
+ ) -> _ot.FitsData | list[_ot.FitsData]:
484
486
  """
485
487
  Loads a FITS file.
486
488
 
@@ -505,7 +507,12 @@ def load_fits(filepath: str, on_gpu: bool = False) -> _ot.FitsData | list[_ot.Fi
505
507
  filepath += ".fits"
506
508
 
507
509
  multi_hdu = False
508
- with _fits.open(filepath) as hdul:
510
+ # memmap=False: with memory mapping (astropy's default) the returned arrays
511
+ # are views on the mmap, which keeps a file descriptor open for as long as
512
+ # the data is alive, even after the `with` block exits. Loading many files
513
+ # (e.g. in `load_cube_from_filelist`) would then hit "Too many open files".
514
+ memmap = kwargs.pop("memmap", True)
515
+ with _fits.open(filepath, memmap=memmap, **kwargs) as hdul:
509
516
  H = len(hdul)
510
517
 
511
518
  # Simple HDU case
@@ -20,6 +20,11 @@ From Python::
20
20
  Via the CLI::
21
21
 
22
22
  calpy -f /path/to/experiment --gui
23
+
24
+ Packages building on opticalib subclass :class:`CalpyGUI` to add their own
25
+ procedures (:class:`~opticalib.gui.plugins.Procedure`, windows built on
26
+ :class:`~opticalib.gui.procedures.base.ProcedureWindow`) and startup
27
+ scripts, then call ``launch_gui(config_path, window_class=MyGUI)``.
23
28
  """
24
29
 
25
30
  import os as _os
@@ -38,7 +43,7 @@ if _os.environ["QT_API"].lower() in _PYQTGRAPH_LIBS:
38
43
  "PYQTGRAPH_QT_LIB", _PYQTGRAPH_LIBS[_os.environ["QT_API"].lower()]
39
44
  )
40
45
 
41
- __all__ = ["CalpyGUI", "launch_gui"]
46
+ __all__ = ["CalpyGUI", "launch_gui", "Procedure", "PROCEDURES"]
42
47
 
43
48
 
44
49
  def __getattr__(name: str):
@@ -49,6 +54,10 @@ def __getattr__(name: str):
49
54
  inside the IPython kernel, which must not pay for (or require) a Qt
50
55
  import; the main window is therefore only loaded on first access.
51
56
  """
57
+ if name in ("Procedure", "PROCEDURES"):
58
+ from . import plugins
59
+
60
+ return getattr(plugins, name)
52
61
  if name in __all__:
53
62
  from . import app
54
63