bec-server 3.148.0__tar.gz → 3.149.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 (211) hide show
  1. {bec_server-3.148.0 → bec_server-3.149.0}/PKG-INFO +1 -1
  2. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/path_optimization.py +65 -20
  3. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_assembler.py +4 -2
  4. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_queue.py +4 -2
  5. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/fermat_scan.py +13 -0
  6. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/legacy_scans.py +11 -20
  7. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/position_generators.py +6 -0
  8. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/scan_actions.py +72 -36
  9. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/scan_base.py +2 -2
  10. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/scan_components.py +20 -25
  11. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/tests/scan_fixtures.py +8 -0
  12. {bec_server-3.148.0 → bec_server-3.149.0}/pyproject.toml +2 -1
  13. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_fermat_scan.py +51 -0
  14. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_scan_actions.py +126 -0
  15. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_scan_components.py +44 -6
  16. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_path_optimization.py +17 -0
  17. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_server_queue.py +59 -2
  18. {bec_server-3.148.0 → bec_server-3.149.0}/.gitignore +0 -0
  19. {bec_server-3.148.0 → bec_server-3.149.0}/.zed/debug.json +0 -0
  20. {bec_server-3.148.0 → bec_server-3.149.0}/README.md +0 -0
  21. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/__init__.py +0 -0
  22. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/__init__.py +0 -0
  23. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/actor.py +0 -0
  24. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/builtin_actor_manager.py +0 -0
  25. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/manager.py +0 -0
  26. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/scan_interlock.py +0 -0
  27. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/actors/worker.py +0 -0
  28. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/bec_server_utils/__init__.py +0 -0
  29. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/bec_server_utils/launch.py +0 -0
  30. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/bec_server_utils/service_handler.py +0 -0
  31. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/bec_server_utils/subprocess_launch.py +0 -0
  32. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/bec_server_utils/tmux_launch.py +0 -0
  33. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/__init__.py +0 -0
  34. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/cli/__init__.py +0 -0
  35. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/cli/launch.py +0 -0
  36. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/dap_server.py +0 -0
  37. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/dap_service.py +0 -0
  38. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/dap_service_manager.py +0 -0
  39. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/image_analysis_service.py +0 -0
  40. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/data_processing/lmfit1d_service.py +0 -0
  41. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/__init__.py +0 -0
  42. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/bec_message_handler.py +0 -0
  43. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/cli/__init__.py +0 -0
  44. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/cli/launch.py +0 -0
  45. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/device_server.py +0 -0
  46. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/devices/__init__.py +0 -0
  47. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/devices/config_update_handler.py +0 -0
  48. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/devices/device_serializer.py +0 -0
  49. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/devices/devicemanager.py +0 -0
  50. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/friendly_device_exceptions.py +0 -0
  51. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/rpc_handler.py +0 -0
  52. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/tests/__init__.py +0 -0
  53. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/device_server/tests/utils.py +0 -0
  54. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/__init__.py +0 -0
  55. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/async_writer.py +0 -0
  56. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/cli/__init__.py +0 -0
  57. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/cli/launch.py +0 -0
  58. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/default_writer.py +0 -0
  59. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/file_writer.py +0 -0
  60. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/file_writer_manager.py +0 -0
  61. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer/merged_dicts.py +0 -0
  62. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer_plugins/__init__.py +0 -0
  63. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/file_writer_plugins/cSAXS.py +0 -0
  64. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/Containerfile.bec_linux_base +0 -0
  65. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/Containerfile.requirements +0 -0
  66. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/Containerfile.worker +0 -0
  67. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/__init__.py +0 -0
  68. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/_dev_runner.py +0 -0
  69. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/builtin_procedures.py +0 -0
  70. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/constants.py +0 -0
  71. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/container_utils.py +0 -0
  72. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/container_worker.py +0 -0
  73. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/manager.py +0 -0
  74. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/oop_worker_base.py +0 -0
  75. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/procedure_registry.py +0 -0
  76. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/protocol.py +0 -0
  77. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/subprocess_worker.py +0 -0
  78. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/procedures/worker_base.py +0 -0
  79. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/__init__.py +0 -0
  80. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/bec_emitter.py +0 -0
  81. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/cli/__init__.py +0 -0
  82. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/cli/launch.py +0 -0
  83. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/emitter.py +0 -0
  84. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_bundler/scan_bundler.py +0 -0
  85. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/__init__.py +0 -0
  86. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/beamline_state_manager.py +0 -0
  87. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/cli/__init__.py +0 -0
  88. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/cli/launch.py +0 -0
  89. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/direct_scan_worker.py +0 -0
  90. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/errors.py +0 -0
  91. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/generator_scan_worker.py +0 -0
  92. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/instruction_handler.py +0 -0
  93. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_guard.py +0 -0
  94. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_gui_models.py +0 -0
  95. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_manager.py +0 -0
  96. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_plugins/__init__.py +0 -0
  97. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_plugins/otf_scan.py +0 -0
  98. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_server.py +0 -0
  99. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_stubs.py +0 -0
  100. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scan_worker.py +0 -0
  101. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/__init__.py +0 -0
  102. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/acquire.py +0 -0
  103. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/cont_line_scan.py +0 -0
  104. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/grid_scan.py +0 -0
  105. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/hexagonal_scan.py +0 -0
  106. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/line_scan.py +0 -0
  107. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/line_sweep_scan.py +0 -0
  108. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/list_scan.py +0 -0
  109. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/log_scan.py +0 -0
  110. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/move.py +0 -0
  111. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/multi_region_grid_scan.py +0 -0
  112. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/multi_region_line_scan.py +0 -0
  113. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/round_roi_scan.py +0 -0
  114. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/round_scan.py +0 -0
  115. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/scan_argument_modifier.py +0 -0
  116. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/scan_modifier.py +0 -0
  117. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/time_scan.py +0 -0
  118. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/scans/updated_move.py +0 -0
  119. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/tests/__init__.py +0 -0
  120. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/tests/fixtures.py +0 -0
  121. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/tests/scan_hook_tests.py +0 -0
  122. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/tests/utils.py +0 -0
  123. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scan_server/types.py +0 -0
  124. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/__init__.py +0 -0
  125. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/__init__.py +0 -0
  126. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/atlas_active_client_emitter.py +0 -0
  127. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/atlas_connector.py +0 -0
  128. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/atlas_forwarder.py +0 -0
  129. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/atlas_metadata_handler.py +0 -0
  130. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/atlas/config_handler.py +0 -0
  131. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/cli/__init__.py +0 -0
  132. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/cli/launch.py +0 -0
  133. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/scihub.py +0 -0
  134. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/service_handler/__init__.py +0 -0
  135. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/scihub/service_handler/service_handler.py +0 -0
  136. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/test/__init__.py +0 -0
  137. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/test/actor_test_utils.py +0 -0
  138. {bec_server-3.148.0 → bec_server-3.149.0}/bec_server/test/helpers.py +0 -0
  139. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_actors/test_actors.py +0 -0
  140. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_bec_server_utils/test_main.py +0 -0
  141. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_bec_server_utils/test_service_handler.py +0 -0
  142. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_bec_server_utils/test_tmux_launch.py +0 -0
  143. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/conftest.py +0 -0
  144. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/test_dap_cli_launch.py +0 -0
  145. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/test_dap_server.py +0 -0
  146. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/test_dap_service_manager.py +0 -0
  147. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/test_image_analysis_service.py +0 -0
  148. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_data_processing/test_lmfit1d_service.py +0 -0
  149. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/conftest.py +0 -0
  150. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_config_handler.py +0 -0
  151. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_device_instructions.py +0 -0
  152. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_device_manager_ds.py +0 -0
  153. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_device_serializer.py +0 -0
  154. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_device_server.py +0 -0
  155. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_device_server_cli_launch.py +0 -0
  156. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_device_server/test_rpc_handler.py +0 -0
  157. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_file_writer/conftest.py +0 -0
  158. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_file_writer/test_async_file_writer.py +0 -0
  159. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_file_writer/test_file_writer.py +0 -0
  160. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_file_writer/test_file_writer_cli_launch.py +0 -0
  161. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_file_writer/test_file_writer_manager.py +0 -0
  162. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_bundler/conftest.py +0 -0
  163. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_bundler/test_bec_emitter.py +0 -0
  164. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_bundler/test_scan_bundler.py +0 -0
  165. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_bundler/test_scan_bundler_cli_launch.py +0 -0
  166. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/conftest.py +0 -0
  167. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/__init__.py +0 -0
  168. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_acquire.py +0 -0
  169. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_cont_line_scan.py +0 -0
  170. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_grid_scan.py +0 -0
  171. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_hexagonal_scan.py +0 -0
  172. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_line_scan.py +0 -0
  173. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_line_sweep_scan.py +0 -0
  174. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_list_scan.py +0 -0
  175. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_log_scan.py +0 -0
  176. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_move_scan.py +0 -0
  177. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_multi_region_grid_scan.py +0 -0
  178. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_multi_region_line_scan.py +0 -0
  179. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_position_generators.py +0 -0
  180. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_relative_scan_return_to_start.py +0 -0
  181. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_round_roi_scan.py +0 -0
  182. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_round_scan.py +0 -0
  183. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_time_scan.py +0 -0
  184. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/scans_v4/test_updated_move_scan.py +0 -0
  185. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_beamline_state_manager.py +0 -0
  186. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_builtin_actor_manager.py +0 -0
  187. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_container_utils.py +0 -0
  188. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_direct_scan_worker.py +0 -0
  189. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_generator_scan_worker.py +0 -0
  190. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_instruction_handler.py +0 -0
  191. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_legacy_scans.py +0 -0
  192. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_procedure_container_worker.py +0 -0
  193. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_procedures.py +0 -0
  194. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_argument_modifier.py +0 -0
  195. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_assembler.py +0 -0
  196. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_guard.py +0 -0
  197. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_gui_models.py +0 -0
  198. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_interlock.py +0 -0
  199. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_modifier.py +0 -0
  200. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_server_cli_launch.py +0 -0
  201. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_server_scan_manager.py +0 -0
  202. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_stub_status.py +0 -0
  203. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_stubs.py +0 -0
  204. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scan_server/test_scan_worker.py +0 -0
  205. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/conftest.py +0 -0
  206. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_atlas_active_client_emitter.py +0 -0
  207. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_atlas_config_handler.py +0 -0
  208. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_atlas_connector.py +0 -0
  209. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_atlas_forwarder.py +0 -0
  210. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_atlas_metadata_handler.py +0 -0
  211. {bec_server-3.148.0 → bec_server-3.149.0}/tests/tests_scihub/test_scihub_cli_launch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec-server
3
- Version: 3.148.0
3
+ Version: 3.149.0
4
4
  Summary: BEC server
5
5
  Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
6
6
  Project-URL: Homepage, https://github.com/bec-project/bec
@@ -1,9 +1,15 @@
1
+ from time import perf_counter
1
2
  from typing import Literal
2
3
 
3
4
  import numpy as np
4
5
  from pydantic import BaseModel
5
6
  from scipy.spatial import cKDTree # type: ignore
6
7
 
8
+ from bec_lib.logger import bec_logger
9
+ from bec_server.scan_server.scans.position_generators import Direction
10
+
11
+ logger = bec_logger.logger
12
+
7
13
 
8
14
  class PathQualityStats(BaseModel):
9
15
  max_jump: float
@@ -15,6 +21,19 @@ class PathQualityStats(BaseModel):
15
21
 
16
22
 
17
23
  class PathOptimizerMixin:
24
+ def _log_optimization_result(
25
+ self, method: str, original_path: np.ndarray, optimized_path: np.ndarray, duration_s: float
26
+ ) -> None:
27
+ original_length = self.get_path_length(original_path)
28
+ optimized_length = self.get_path_length(optimized_path)
29
+ reduced_by = original_length - optimized_length
30
+ reduction_pct = (reduced_by / original_length * 100) if original_length > 0 else 0.0
31
+
32
+ logger.info(
33
+ f"Path optimizer ({method}) reduced path length from {original_length:.4f} to {optimized_length:.4f} "
34
+ f"({reduced_by:.4f} reduction, {reduction_pct:.2f}% decrease) in {duration_s:.4f} seconds."
35
+ )
36
+
18
37
  def get_radius(self, pos: np.ndarray) -> np.ndarray:
19
38
  """
20
39
  Calculate the radial distance from the origin for each position
@@ -31,9 +50,10 @@ class PathOptimizerMixin:
31
50
  self,
32
51
  positions: np.ndarray,
33
52
  corridor_size: float | None = None,
34
- sort_axis: int = 1,
53
+ fast_axis: Literal[0, 1] = 0,
35
54
  num_iterations: int = 1,
36
- preferred_direction: int | None = None,
55
+ first_corridor_direction: Direction | Literal[-1, 1] = Direction.ASCENDING,
56
+ snaked: bool = True,
37
57
  corridor_estimation: Literal["density", "median_distance"] = "median_distance",
38
58
  ):
39
59
  """
@@ -45,12 +65,15 @@ class PathOptimizerMixin:
45
65
  Args:
46
66
  positions (np.ndarray): Array of positions
47
67
  corridor_size (float, optional): Width of each corridor. Defaults to None (auto-estimated).
48
- sort_axis (int, optional): Axis along which to create corridors (0 or 1). Defaults to 1.
68
+ fast_axis (Literal[0, 1], optional): Axis traversed within each corridor (0 or 1). Defaults to 0.
49
69
  num_iterations (int, optional): Number of corridor sizes to try. Defaults to 1.
50
- preferred_direction (int | None, optional): Preferred direction for the primary axis (1 or -1).
51
- If None, alternates direction for each corridor.
52
- corridor_estimation (str, optional): Method for estimating corridor size if not provided.
53
- Options are "density" or "median_distance". Defaults to "density".
70
+ first_corridor_direction (Direction | Literal[-1, 1], optional): Traversal direction of the first
71
+ corridor along the fast axis. Positive means ascending, negative means
72
+ descending. Defaults to Direction.ASCENDING.
73
+ snaked (bool, optional): If True, alternate the traversal direction between
74
+ successive corridors. If False, keep the same direction in every corridor.
75
+ corridor_estimation (Literal["density", "median_distance"], optional): Method for estimating corridor size if not provided.
76
+ Options are "density" or "median_distance". Defaults to "median_distance".
54
77
 
55
78
  Returns:
56
79
  np.ndarray: Optimized positions
@@ -58,6 +81,9 @@ class PathOptimizerMixin:
58
81
  if positions.ndim != 2 or positions.shape[1] < 2:
59
82
  return positions
60
83
 
84
+ start_time = perf_counter()
85
+ original_path = positions
86
+
61
87
  # Estimate corridor size if needed
62
88
  if corridor_size is None:
63
89
  if corridor_estimation == "density":
@@ -70,8 +96,14 @@ class PathOptimizerMixin:
70
96
  'Choose "density" or "median_distance".'
71
97
  )
72
98
 
73
- axis_vals = positions[:, sort_axis]
74
- sec_axis = int(not sort_axis)
99
+ if not isinstance(first_corridor_direction, Direction):
100
+ first_corridor_direction = Direction(first_corridor_direction)
101
+
102
+ if fast_axis not in (0, 1):
103
+ raise ValueError("fast_axis must be 0 or 1")
104
+
105
+ slow_axis = int(not fast_axis)
106
+ axis_vals = positions[:, slow_axis]
75
107
 
76
108
  best_length = np.inf
77
109
  best_path = positions
@@ -97,17 +129,15 @@ class PathOptimizerMixin:
97
129
  if block.size == 0:
98
130
  continue
99
131
 
100
- # Sort within corridor along secondary axis
101
- block = block[np.argsort(positions[block, sec_axis])]
132
+ # Sort within corridor along fast axis
133
+ block = block[np.argsort(positions[block, fast_axis])]
102
134
 
103
135
  # Direction handling
104
- if preferred_direction is not None:
105
- if preferred_direction < 0:
106
- block = block[::-1]
107
- else:
108
- # Alternate direction between corridors
109
- if step % 2 == 0:
110
- block = block[::-1]
136
+ direction = first_corridor_direction
137
+ if snaked and step % 2 == 1:
138
+ direction = Direction(-direction)
139
+ if direction == Direction.DESCENDING:
140
+ block = block[::-1]
111
141
 
112
142
  index_sorted.append(block)
113
143
 
@@ -122,6 +152,9 @@ class PathOptimizerMixin:
122
152
  best_length = path_length
123
153
  best_path = path
124
154
 
155
+ self._log_optimization_result(
156
+ "corridor", original_path, best_path, perf_counter() - start_time
157
+ )
125
158
  return best_path
126
159
 
127
160
  def _corridor_estimate_density(self, positions: np.ndarray) -> float:
@@ -182,6 +215,9 @@ class PathOptimizerMixin:
182
215
  if pos.ndim != 2 or pos.shape[1] < 2 or len(pos) < 2:
183
216
  return pos
184
217
 
218
+ start_time = perf_counter()
219
+ original_path = pos
220
+
185
221
  # Calculate radii once
186
222
  radii = self.get_radius(pos)
187
223
  max_rad = np.max(radii)
@@ -227,7 +263,9 @@ class PathOptimizerMixin:
227
263
  best_score = score
228
264
  best_path = optimized_path
229
265
 
230
- return best_path if best_path is not None else pos
266
+ result = best_path if best_path is not None else pos
267
+ self._log_optimization_result("shell", original_path, result, perf_counter() - start_time)
268
+ return result
231
269
 
232
270
  def _estimate_shell_width(self, pos: np.ndarray, radii: np.ndarray, max_rad: float) -> float:
233
271
  """
@@ -413,6 +451,9 @@ class PathOptimizerMixin:
413
451
  if len(positions) <= 1:
414
452
  return positions
415
453
 
454
+ start_time = perf_counter()
455
+ original_path = positions
456
+
416
457
  n_points = len(positions)
417
458
 
418
459
  # Use a copy to avoid modifying the original
@@ -455,4 +496,8 @@ class PathOptimizerMixin:
455
496
  path.append(current_pos)
456
497
  remaining_indices.remove(original_idx)
457
498
 
458
- return np.array(path)
499
+ result = np.array(path)
500
+ self._log_optimization_result(
501
+ "nearest_neighbor", original_path, result, perf_counter() - start_time
502
+ )
503
+ return result
@@ -97,13 +97,15 @@ class ScanAssembler:
97
97
  )
98
98
  return scan_instance
99
99
 
100
- def assemble_direct_scan(self, msg: messages.ScanQueueMessage, scan_id: str) -> ScanBaseV4:
100
+ def assemble_direct_scan(
101
+ self, msg: messages.ScanQueueMessage, scan_id: str | None
102
+ ) -> ScanBaseV4:
101
103
  """Assemble the device instructions for a given ScanQueueMessage.
102
104
  This will be achieved by calling the specified class (must be a derived class of ScanBaseV4)
103
105
 
104
106
  Args:
105
107
  msg (messages.ScanQueueMessage): scan queue message for which the instruction should be assembled
106
- scan_id (str): scan id of the scan
108
+ scan_id (str | None): scan id of the scan
107
109
 
108
110
  Raises:
109
111
  ScanAbortion: Raised if the scan initialization fails.
@@ -1594,7 +1594,9 @@ class DirectInstructionQueueItem:
1594
1594
  Args:
1595
1595
  msg (ScanQueueMessage): the scan queue message containing the scan information
1596
1596
  """
1597
- scan = self.assembler.assemble_direct_scan(msg, scan_id=self._scan_id)
1597
+ scan_cls = self.assembler.scan_manager.scan_dict[msg.scan_type]
1598
+ scan_id = self._scan_id if getattr(scan_cls, "is_scan", True) else None
1599
+ scan = self.assembler.assemble_direct_scan(msg, scan_id=scan_id)
1598
1600
  self.scans.append(scan)
1599
1601
  self.scan_msgs.append(msg)
1600
1602
 
@@ -1718,7 +1720,7 @@ class DirectInstructionQueueItem:
1718
1720
  def _set_scan_as_active(self, scan: ScanBase_v4):
1719
1721
  """set a given scan as the active scan"""
1720
1722
  self.active_scan = scan
1721
- if scan.scan_info.scan_number is None:
1723
+ if scan.scan_info.scan_number is None and scan.is_scan:
1722
1724
  with self.parent.queue_manager._lock:
1723
1725
  self.parent.queue_manager.parent.scan_number += 1
1724
1726
  if not self.scan_msgs[self.scans.index(scan)].metadata.get("dataset_id_on_hold"):
@@ -24,6 +24,7 @@ from bec_lib.device import DeviceBase
24
24
  from bec_lib.logger import bec_logger
25
25
  from bec_lib.scan_args import DefaultArgType, ScanArgument, Units
26
26
  from bec_server.scan_server.scans import position_generators
27
+ from bec_server.scan_server.scans.position_generators import Direction
27
28
  from bec_server.scan_server.scans.scan_base import ScanBase, ScanType
28
29
  from bec_server.scan_server.scans.scan_modifier import scan_hook
29
30
 
@@ -173,6 +174,18 @@ class FermatSpiralScan(ScanBase):
173
174
  self.start_positions = self.components.get_start_positions(self.motors)
174
175
  self.positions += self.start_positions
175
176
 
177
+ if self.optim_trajectory:
178
+ self.positions = self.components.optimize_trajectory(
179
+ self.positions,
180
+ optimization_type=self.optim_trajectory,
181
+ fast_axis=0,
182
+ first_direction=(
183
+ Direction.ASCENDING
184
+ if self.motor1_start_stop[1] > self.motor1_start_stop[0]
185
+ else Direction.DESCENDING
186
+ ),
187
+ )
188
+
176
189
  self.components.check_limits(self.motors, self.positions)
177
190
 
178
191
  self.update_scan_info(
@@ -19,7 +19,7 @@ from bec_lib.logger import bec_logger
19
19
  from bec_server.scan_server.instruction_handler import InstructionHandler
20
20
 
21
21
  from ..errors import LimitError, ScanAbortion
22
- from ..path_optimization import PathOptimizerMixin
22
+ from ..path_optimization import Direction, PathOptimizerMixin
23
23
  from ..scan_stubs import ScanStubs
24
24
 
25
25
  logger = bec_logger.logger
@@ -519,26 +519,14 @@ class ScanBase(RequestBase, PathOptimizerMixin):
519
519
  if not self.optim_trajectory:
520
520
  return
521
521
 
522
- # Get preferred directions from scan parameters if available
523
- preferred_directions = getattr(self, "preferred_directions", None)
524
-
525
522
  if self.optim_trajectory == "corridor":
526
- # For corridor optimization, we use the primary axis preferred direction
527
- if preferred_directions and len(preferred_directions) > 0:
528
- primary_axis = getattr(self, "sort_axis", 1)
529
- preferred_direction = (
530
- preferred_directions[primary_axis]
531
- if len(preferred_directions) > primary_axis
532
- else None
533
- )
534
- self.positions = self.optimize_corridor(
535
- self.positions,
536
- num_iterations=5,
537
- sort_axis=primary_axis,
538
- preferred_direction=preferred_direction,
539
- )
540
- else:
541
- self.positions = self.optimize_corridor(self.positions, num_iterations=5)
523
+ # Corridor traversal direction follows the first pass along the axis within each corridor.
524
+ self.positions = self.optimize_corridor(
525
+ self.positions,
526
+ num_iterations=5,
527
+ fast_axis=getattr(self, "fast_axis", 0),
528
+ first_corridor_direction=getattr(self, "first_direction", 1),
529
+ )
542
530
  return
543
531
 
544
532
  if self.optim_trajectory == "shell":
@@ -1102,6 +1090,9 @@ class FermatSpiralScan(ScanBase):
1102
1090
  self.stop_motor2 = stop_motor2
1103
1091
  self.step = step
1104
1092
  self.spiral_type = spiral_type
1093
+ self.first_direction = (
1094
+ Direction.ASCENDING if self.stop_motor1 > self.start_motor1 else Direction.DESCENDING
1095
+ )
1105
1096
 
1106
1097
  def update_scan_motors(self):
1107
1098
  self.scan_motors = [self.motor1, self.motor2]
@@ -1,10 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import enum
3
4
  from collections.abc import Iterator, Sequence
4
5
 
5
6
  import numpy as np
6
7
 
7
8
 
9
+ class Direction(int, enum.Enum):
10
+ ASCENDING = 1
11
+ DESCENDING = -1
12
+
13
+
8
14
  def rotate_points(
9
15
  points: np.ndarray, angle: float, center: tuple[float, float] | None = None
10
16
  ) -> np.ndarray:
@@ -153,15 +153,7 @@ class ScanActions:
153
153
 
154
154
  # We support str and DeviceBase inputs as well as lists of those.
155
155
  # We convert them to a list of device names for easier processing.
156
- if isinstance(device, list):
157
- device_names = []
158
- for dev in device:
159
- if isinstance(dev, DeviceBase):
160
- device_names.append(dev.name)
161
- else:
162
- device_names.append(dev)
163
- else:
164
- device_names = [device.name if isinstance(device, DeviceBase) else device]
156
+ device_names = self._normalize_device_names(device)
165
157
  if len(device_names) == 1:
166
158
  device_names = device_names[0]
167
159
  status = self._create_status(name=status_name or f"stage_{device_names}")
@@ -290,7 +282,7 @@ class ScanActions:
290
282
 
291
283
  status = self._create_status(is_container=True, name="set")
292
284
  for dev, val in zip(devices, values, strict=False):
293
- device_name = dev.name if isinstance(dev, DeviceBase) else dev
285
+ device_name = self._normalize_device_name(dev)
294
286
  sub_status = self._create_status(name=f"set_{device_name}")
295
287
  instr = messages.DeviceInstructionMessage(
296
288
  device=device_name,
@@ -320,7 +312,7 @@ class ScanActions:
320
312
  Returns:
321
313
  ScanStubStatus: status object to track the kickoff process
322
314
  """
323
- device_name = device.name if isinstance(device, DeviceBase) else device
315
+ device_name = self._normalize_device_name(device)
324
316
  status = self._create_status(name=f"kickoff_{device_name}")
325
317
 
326
318
  instr = messages.DeviceInstructionMessage(
@@ -347,7 +339,7 @@ class ScanActions:
347
339
  Returns:
348
340
  ScanStubStatus: status object to track the completion process
349
341
  """
350
- device_name = device.name if isinstance(device, DeviceBase) else device
342
+ device_name = self._normalize_device_name(device)
351
343
  status = self._create_status(name=f"complete_{device_name}")
352
344
 
353
345
  instr = messages.DeviceInstructionMessage(
@@ -620,7 +612,7 @@ class ScanActions:
620
612
  Returns:
621
613
  ScanStubStatus: status object to track the unstaging process
622
614
  """
623
- device_name = device.name if isinstance(device, DeviceBase) else device
615
+ device_name = self._normalize_device_name(device)
624
616
  status = self._create_status(name=f"unstage_{device_name}")
625
617
 
626
618
  instr = messages.DeviceInstructionMessage(
@@ -687,7 +679,7 @@ class ScanActions:
687
679
  request_id (str, optional): request ID to associate the readback instruction with. If None, the scan's RID will be used. Defaults to None.
688
680
  """
689
681
  request_id = request_id or self._scan.scan_info.metadata["RID"]
690
- device_names = [dev.name if isinstance(dev, DeviceBase) else dev for dev in devices]
682
+ device_names = self._normalize_device_names(devices)
691
683
  scan_report_instruction = {
692
684
  "readback": {"RID": request_id, "devices": device_names, "start": start, "end": stop}
693
685
  }
@@ -705,10 +697,7 @@ class ScanActions:
705
697
  Args:
706
698
  device (str | DeviceBase): name of the device or DeviceBase instance to report
707
699
  """
708
- if isinstance(device, DeviceBase):
709
- device_name = device.name
710
- else:
711
- device_name = device
700
+ device_name = self._normalize_device_name(device)
712
701
  scan_report_instruction = {"device_progress": [device_name]}
713
702
  self._scan.scan_info.scan_report_instructions.append(scan_report_instruction)
714
703
  if self._update_queue_info_callback is not None:
@@ -762,10 +751,7 @@ class ScanActions:
762
751
  devices = [devices]
763
752
 
764
753
  for device in devices:
765
- if isinstance(device, DeviceBase):
766
- device_name = device.name
767
- else:
768
- device_name = device
754
+ device_name = self._normalize_device_name(device)
769
755
  self._scan.scan_info.readout_priority_modification[priority].append(device_name)
770
756
 
771
757
  def close_scan(self):
@@ -834,13 +820,15 @@ class ScanActions:
834
820
  """
835
821
  if isinstance(device, list):
836
822
  for dev in device:
837
- device_name = dev.name if isinstance(dev, DeviceBase) else dev
823
+ device_name = self._normalize_device_name(dev)
838
824
  self._devices_with_required_response.add(device_name)
839
825
  else:
840
- device_name = device.name if isinstance(device, DeviceBase) else device
826
+ device_name = self._normalize_device_name(device)
841
827
  self._devices_with_required_response.add(device_name)
842
828
 
843
- def rpc_call(self, device: str, func_name: str, *args, **kwargs) -> Any | ScanStubStatus:
829
+ def rpc_call(
830
+ self, device: str | DeviceBase, func_name: str, *args, **kwargs
831
+ ) -> Any | ScanStubStatus:
844
832
  """
845
833
  Make an RPC call to a device. This will call the given function on the device with the given arguments.
846
834
  The device server will execute the function and return the result in the instruction response.
@@ -864,26 +852,49 @@ class ScanActions:
864
852
  Any | ScanStubStatus: The result of the RPC call or a ScanStubStatus object if the result is a status object.
865
853
 
866
854
  """
867
- status = self._create_status(name=f"rpc_{device}_{func_name}")
868
855
  rpc_id = str(uuid.uuid4())
856
+ status = self.rpc_call_no_wait(device, func_name, rpc_id, *args, **kwargs)
857
+ status.wait(resolve_on_known_type=True)
858
+ if status._result_is_status:
859
+ return status
860
+ return status.result
861
+
862
+ def rpc_call_no_wait(
863
+ self, device: str | DeviceBase, func_name: str, rpc_id: str, *args, **kwargs
864
+ ) -> ScanStubStatus:
865
+ """
866
+ Make an RPC call to a device without waiting for the result. This will call the given function on the device with the given arguments.
867
+ The device server will execute the function and return the result in the instruction response.
868
+ This method is a low-level interface to call arbitrary functions on the device server and should be used with caution.
869
+
870
+ Args:
871
+ device (str | DeviceBase): name of the device or device instance to call the function on
872
+ func_name (str): name of the function to call on the device
873
+ rpc_id (str): unique identifier for the RPC call, used to match the response with the request
874
+ *args: positional arguments to pass to the function
875
+ **kwargs: keyword arguments to pass to the function
876
+
877
+ Returns:
878
+ ScanStubStatus: A ScanStubStatus object that can be used to wait for the result of the RPC call.
879
+ """
880
+ device_name = self._normalize_device_name(device)
881
+ status = self._create_status(name=f"rpc_{device_name}_{func_name}")
882
+
869
883
  parameter = {
870
- "device": device,
884
+ "device": device_name,
871
885
  "func": func_name,
872
886
  "rpc_id": rpc_id,
873
887
  "args": args,
874
888
  "kwargs": kwargs,
875
889
  }
876
890
  msg = messages.DeviceInstructionMessage(
877
- device=device,
891
+ device=device_name,
878
892
  action="rpc",
879
893
  parameter=parameter,
880
894
  metadata={"device_instr_id": status._device_instr_id},
881
895
  )
882
896
  self._send(msg)
883
- status.wait(resolve_on_known_type=True)
884
- if status._result_is_status:
885
- return status
886
- return status.result
897
+ return status
887
898
 
888
899
  def send_client_info(self, message: str):
889
900
  """
@@ -959,12 +970,37 @@ class ScanActions:
959
970
  metadata[key] = value + self._metadata_suffix
960
971
  return metadata
961
972
 
962
- def _normalize_device_names(
963
- self, devices: str | DeviceBase | list[str | DeviceBase]
964
- ) -> list[str]:
973
+ @staticmethod
974
+ def _normalize_device_name(device: str | DeviceBase) -> str:
975
+ """
976
+ Normalize the device name to a string. If the device is a DeviceBase instance, return its dotted name.
977
+ We use the dotted name because it is uniquely identifying a device in the device tree, even for
978
+ sub-devices.
979
+
980
+ Args:
981
+ device (str | DeviceBase): device name or DeviceBase instance
982
+
983
+ Returns:
984
+ str: normalized device name
985
+ """
986
+ if isinstance(device, DeviceBase):
987
+ return getattr(device, "dotted_name", None) or device.name
988
+ return device
989
+
990
+ @staticmethod
991
+ def _normalize_device_names(devices: str | DeviceBase | list[str | DeviceBase]) -> list[str]:
992
+ """
993
+ Normalize a list of device names to a list of strings. If the devices are DeviceBase instances, return their dotted names.
994
+
995
+ Args:
996
+ devices (str | DeviceBase | list[str | DeviceBase]): device name(s) or DeviceBase instance(s)
997
+
998
+ Returns:
999
+ list[str]: list of normalized device names
1000
+ """
965
1001
  if not isinstance(devices, list):
966
1002
  devices = [devices]
967
- return [dev.name if isinstance(dev, DeviceBase) else dev for dev in devices]
1003
+ return [ScanActions._normalize_device_name(dev) for dev in devices]
968
1004
 
969
1005
  def _get_monitored_device_names(self) -> list[str]:
970
1006
  monitored_devices = [
@@ -54,7 +54,7 @@ class ScanInfo(BaseModel):
54
54
 
55
55
  # General scan information
56
56
  scan_name: Annotated[str, Field(description="Name of the scan type, e.g. 'grid_scan'")]
57
- scan_id: Annotated[str, Field(description="Unique identifier for the scan")]
57
+ scan_id: Annotated[str | None, Field(description="Unique identifier for the scan")] = None
58
58
  scan_type: Annotated[
59
59
  ScanType | None,
60
60
  Field(
@@ -167,7 +167,7 @@ class ScanBase(ABC):
167
167
 
168
168
  def __init__(
169
169
  self,
170
- scan_id: str,
170
+ scan_id: str | None,
171
171
  redis_connector: RedisConnector,
172
172
  device_manager: DeviceManager,
173
173
  instruction_handler: InstructionHandler,
@@ -8,6 +8,7 @@ import numpy as np
8
8
  from bec_lib.device import DeviceBase
9
9
  from bec_server.scan_server.errors import LimitError
10
10
  from bec_server.scan_server.path_optimization import PathOptimizerMixin
11
+ from bec_server.scan_server.scans.position_generators import Direction
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from bec_lib.devicemanager import DeviceContainer
@@ -160,49 +161,43 @@ class ScanComponents:
160
161
  self,
161
162
  positions: np.ndarray,
162
163
  optimization_type: Literal["corridor", "shell", "nearest"] = "corridor",
163
- primary_axis: int = 1,
164
- preferred_directions: list[int] | None = None,
164
+ fast_axis: Literal[0, 1] = 0,
165
+ first_direction: Literal[-1, 1] | Direction = Direction.ASCENDING,
166
+ snaked: bool = True,
165
167
  corridor_size: float | None = None,
166
168
  num_iterations: int = 5,
167
169
  ) -> np.ndarray:
168
170
  """
169
171
  Optimize the trajectory of the scan by reordering the positions. This can help to minimize the movement time of the motors.
172
+
173
+ Important note: The optimization is only done for 2D scans. For higher-dimensional scans, the positions are not reordered and simply
174
+ returned as they are.
175
+
170
176
  The optimization can be done in different ways, depending on the optimization_type parameter:
171
- - "corridor": optimize the trajectory in a corridor-like way, where the scan moves back and forth along the primary axis. This is typically a good choice for grid scans. If preferred_directions are provided, the optimizer will try to optimize the trajectory in a way that minimizes the movement in the non-preferred direction.
177
+ - "corridor": optimize the trajectory in a corridor-like way, where the scan progresses between corridors along the slow axis and traverses each corridor along the fast axis. This is typically a good choice for grid scans. If first_direction is provided, it sets the traversal direction of the first corridor and snaked controls whether later corridors alternate direction, producing a snaked path along the fast axis.
172
178
  - "shell": optimize the trajectory in a shell-like way, where the scan moves in a spiral from the outside to the inside. This is typically a good choice for round scans.
173
179
  - "nearest": optimize the trajectory by always moving to the nearest next point. This is typically a good choice for random scans.
174
180
 
175
181
  Args:
176
182
  positions (np.ndarray): Array of positions to optimize, shape (num_points, num_motors).
177
183
  optimization_type (str, optional): Type of optimization to perform. Defaults to "corridor".
178
- primary_axis (int, optional): Primary axis for corridor optimization. Defaults to 1.
179
- preferred_directions (list[int] | None, optional): List of preferred directions for the non-primary axes. Each entry should be -1, 0, or 1, indicating the preferred direction of movement along that axis. The length of the list should be equal to the number of non-primary axes. Defaults to None, which means no preferred directions.
184
+ fast_axis (int, optional): Fast axis for corridor optimization. Defaults to 0.
185
+ first_direction (Direction | Literal[-1, 1], optional): Traversal direction for the first corridor along the fast axis. Positive means ascending, negative means descending. Defaults to Direction.ASCENDING.
186
+ snaked (bool, optional): If True, alternate direction between corridors. Defaults to True.
180
187
  corridor_size (float | None, optional): Size of the corridor for corridor optimization. Defaults to None, which means the default corridor size will be used.
181
188
  Returns:
182
189
  np.ndarray: Optimized array of positions, shape (num_points, num_motors).
183
190
  """
184
191
 
185
192
  if optimization_type == "corridor":
186
- if preferred_directions is None or len(preferred_directions) == 0:
187
- positions = self._path_optimizer.optimize_corridor(
188
- positions,
189
- num_iterations=num_iterations,
190
- corridor_size=corridor_size,
191
- sort_axis=primary_axis,
192
- )
193
- else:
194
- preferred_direction = (
195
- preferred_directions[primary_axis]
196
- if len(preferred_directions) > primary_axis
197
- else None
198
- )
199
- positions = self._path_optimizer.optimize_corridor(
200
- positions,
201
- num_iterations=num_iterations,
202
- sort_axis=primary_axis,
203
- preferred_direction=preferred_direction,
204
- corridor_size=corridor_size,
205
- )
193
+ positions = self._path_optimizer.optimize_corridor(
194
+ positions,
195
+ num_iterations=num_iterations,
196
+ corridor_size=corridor_size,
197
+ fast_axis=fast_axis,
198
+ first_corridor_direction=first_direction,
199
+ snaked=snaked,
200
+ )
206
201
 
207
202
  elif optimization_type == "shell":
208
203
  positions = self._path_optimizer.optimize_shell(
@@ -119,6 +119,10 @@ class _MockDevice(DeviceBase):
119
119
  def full_name(self):
120
120
  return self.name
121
121
 
122
+ @property
123
+ def dotted_name(self):
124
+ return self.name
125
+
122
126
  @property
123
127
  def limits(self):
124
128
  return self._limits
@@ -222,6 +226,10 @@ class MockCustomDevice(DeviceBase):
222
226
  def full_name(self):
223
227
  return self.name
224
228
 
229
+ @property
230
+ def dotted_name(self):
231
+ return self.name
232
+
225
233
  @property
226
234
  def limits(self):
227
235
  return self._limits
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec-server"
7
- version = "3.148.0"
7
+ version = "3.149.0"
8
8
  description = "BEC server"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -135,6 +135,7 @@ Homepage = "https://github.com/bec-project/bec"
135
135
 
136
136
 
137
137
 
138
+
138
139
 
139
140
 
140
141
  [tool.hatch.build.targets.wheel]