mx-bluesky 0.3.1__py3-none-any.whl → 1.2.0__py3-none-any.whl

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 (142) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +3 -0
  3. mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
  4. mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
  5. mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
  6. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
  7. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
  8. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
  9. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
  10. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  11. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
  12. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
  13. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
  14. mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
  15. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
  16. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
  17. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
  18. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
  19. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
  20. mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
  21. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  22. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
  23. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
  24. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
  25. mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
  26. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
  27. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
  28. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
  29. mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
  30. mx_bluesky/hyperion/__init__.py +1 -0
  31. mx_bluesky/hyperion/__main__.py +374 -0
  32. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  33. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  34. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  35. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  36. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  37. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  38. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  39. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  40. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  41. mx_bluesky/hyperion/device_setup_plans/utils.py +55 -0
  42. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  43. mx_bluesky/hyperion/exceptions.py +47 -0
  44. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  45. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +93 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +537 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  50. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  51. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  53. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  54. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +237 -0
  55. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +162 -0
  56. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  57. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +60 -0
  58. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +64 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +62 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +86 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  80. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  81. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  85. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  86. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +27 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  88. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  89. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  90. mx_bluesky/hyperion/log.py +99 -0
  91. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  92. mx_bluesky/hyperion/parameters/cli.py +68 -0
  93. mx_bluesky/{parameters → hyperion/parameters}/components.py +80 -26
  94. mx_bluesky/hyperion/parameters/constants.py +158 -0
  95. mx_bluesky/hyperion/parameters/gridscan.py +221 -0
  96. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  97. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  98. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  99. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  100. mx_bluesky/hyperion/tracing.py +28 -0
  101. mx_bluesky/hyperion/utils/context.py +84 -0
  102. mx_bluesky/hyperion/utils/utils.py +25 -0
  103. mx_bluesky/hyperion/utils/validation.py +196 -0
  104. mx_bluesky/jupyter_example.ipynb +3 -2
  105. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +26 -11
  106. mx_bluesky-1.2.0.dist-info/RECORD +140 -0
  107. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +1 -1
  108. mx_bluesky-1.2.0.dist-info/entry_points.txt +8 -0
  109. mx_bluesky/i04/__init__.py +0 -3
  110. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  111. mx_bluesky/parameters/__init__.py +0 -31
  112. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  113. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  114. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  115. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  116. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  119. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  137. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  138. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  139. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  140. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  141. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
  142. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
6
+ PlanReactiveCallback,
7
+ )
8
+ from mx_bluesky.hyperion.external_interaction.nexus.nexus_utils import (
9
+ create_beam_and_attenuator_parameters,
10
+ vds_type_based_on_bit_depth,
11
+ )
12
+ from mx_bluesky.hyperion.external_interaction.nexus.write_nexus import NexusWriter
13
+ from mx_bluesky.hyperion.log import NEXUS_LOGGER
14
+ from mx_bluesky.hyperion.parameters.constants import CONST
15
+ from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
16
+
17
+ if TYPE_CHECKING:
18
+ from event_model.documents import Event, EventDescriptor, RunStart
19
+
20
+
21
+ class GridscanNexusFileCallback(PlanReactiveCallback):
22
+ """Callback class to handle the creation of Nexus files based on experiment \
23
+ parameters. Initialises on recieving a 'start' document for the \
24
+ 'run_gridscan_move_and_tidy' sub plan, which must also contain the run parameters, \
25
+ as metadata under the 'hyperion_internal_parameters' key. Actually writes the \
26
+ nexus files on updates the timestamps on recieving the 'ispyb_reading_hardware' event \
27
+ document, and finalises the files on getting a 'stop' document for the whole run.
28
+
29
+ To use, subscribe the Bluesky RunEngine to an instance of this class.
30
+ E.g.:
31
+ nexus_file_handler_callback = NexusFileCallback(parameters)
32
+ RE.subscribe(nexus_file_handler_callback)
33
+ Or decorate a plan using bluesky.preprocessors.subs_decorator.
34
+
35
+ See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
36
+ """
37
+
38
+ def __init__(self) -> None:
39
+ super().__init__(NEXUS_LOGGER)
40
+ self.run_start_uid: str | None = None
41
+ self.nexus_writer_1: NexusWriter | None = None
42
+ self.nexus_writer_2: NexusWriter | None = None
43
+ self.descriptors: dict[str, EventDescriptor] = {}
44
+ self.log = NEXUS_LOGGER
45
+
46
+ def activity_gated_start(self, doc: RunStart):
47
+ if doc.get("subplan_name") == CONST.PLAN.GRIDSCAN_OUTER:
48
+ json_params = doc.get("hyperion_parameters")
49
+ NEXUS_LOGGER.info(
50
+ f"Nexus writer received start document with experiment parameters {json_params}"
51
+ )
52
+ parameters = ThreeDGridScan.from_json(json_params)
53
+ d_size = parameters.detector_params.detector_size_constants.det_size_pixels
54
+ grid_n_img_1 = parameters.scan_indices[1]
55
+ grid_n_img_2 = parameters.num_images - grid_n_img_1
56
+ data_shape_1 = (grid_n_img_1, d_size.width, d_size.height)
57
+ data_shape_2 = (grid_n_img_2, d_size.width, d_size.height)
58
+ run_number_2 = parameters.detector_params.run_number + 1
59
+ self.nexus_writer_1 = NexusWriter(
60
+ parameters, data_shape_1, parameters.scan_points_first_grid
61
+ )
62
+ self.nexus_writer_2 = NexusWriter(
63
+ parameters,
64
+ data_shape_2,
65
+ parameters.scan_points_second_grid,
66
+ run_number=run_number_2,
67
+ vds_start_index=parameters.scan_indices[1],
68
+ omega_start_deg=90,
69
+ )
70
+ self.run_start_uid = doc.get("uid")
71
+
72
+ def activity_gated_descriptor(self, doc: EventDescriptor):
73
+ self.descriptors[doc["uid"]] = doc
74
+
75
+ def activity_gated_event(self, doc: Event) -> Event | None:
76
+ assert (event_descriptor := self.descriptors.get(doc["descriptor"])) is not None
77
+ if event_descriptor.get("name") == CONST.DESCRIPTORS.HARDWARE_READ_DURING:
78
+ data = doc["data"]
79
+ for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]:
80
+ assert nexus_writer, "Nexus callback did not receive start doc"
81
+ (
82
+ nexus_writer.beam,
83
+ nexus_writer.attenuator,
84
+ ) = create_beam_and_attenuator_parameters(
85
+ data["dcm-energy_in_kev"],
86
+ data["flux_flux_reading"],
87
+ data["attenuator-actual_transmission"],
88
+ )
89
+ vds_data_type = vds_type_based_on_bit_depth(
90
+ doc["data"]["eiger_bit_depth"]
91
+ )
92
+ nexus_writer.create_nexus_file(vds_data_type)
93
+ NEXUS_LOGGER.info(f"Nexus file created at {nexus_writer.data_filename}")
94
+
95
+ return super().activity_gated_event(doc)
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from bluesky.callbacks import CallbackBase
6
+ from dodal.devices.zocalo import ZocaloStartInfo, ZocaloTrigger
7
+
8
+ from mx_bluesky.hyperion.external_interaction.exceptions import ISPyBDepositionNotMade
9
+ from mx_bluesky.hyperion.log import ISPYB_LOGGER
10
+ from mx_bluesky.hyperion.parameters.constants import CONST
11
+ from mx_bluesky.hyperion.utils.utils import number_of_frames_from_scan_spec
12
+
13
+ if TYPE_CHECKING:
14
+ from event_model.documents import Event, EventDescriptor, RunStart, RunStop
15
+
16
+
17
+ class ZocaloCallback(CallbackBase):
18
+ """Callback class to handle the triggering of Zocalo processing.
19
+ Sends zocalo a run_start signal on receiving a start document for the specified
20
+ sub-plan, and sends a run_end signal on receiving a stop document for the same plan.
21
+
22
+ The metadata of the sub-plan this starts on must include a zocalo_environment.
23
+
24
+ Shouldn't be subscribed directly to the RunEngine, instead should be passed to the
25
+ `emit` argument of an ISPyB callback which appends DCIDs to the relevant start doc.
26
+ """
27
+
28
+ def _reset_state(self):
29
+ self.run_uid: str | None = None
30
+ self.triggering_plan: str | None = None
31
+ self.zocalo_interactor: ZocaloTrigger | None = None
32
+ self.zocalo_info: list[ZocaloStartInfo] = []
33
+ self.descriptors: dict[str, EventDescriptor] = {}
34
+
35
+ def __init__(
36
+ self,
37
+ ):
38
+ super().__init__()
39
+ self._reset_state()
40
+
41
+ def start(self, doc: RunStart):
42
+ ISPYB_LOGGER.info("Zocalo handler received start document.")
43
+ if triggering_plan := doc.get(CONST.TRIGGER.ZOCALO):
44
+ self.triggering_plan = triggering_plan
45
+ assert isinstance(zocalo_environment := doc.get("zocalo_environment"), str)
46
+ ISPYB_LOGGER.info(f"Zocalo environment set to {zocalo_environment}.")
47
+ self.zocalo_interactor = ZocaloTrigger(zocalo_environment)
48
+
49
+ if self.triggering_plan and doc.get("subplan_name") == self.triggering_plan:
50
+ self.run_uid = doc.get("uid")
51
+ assert isinstance(scan_points := doc.get("scan_points"), list)
52
+ if (
53
+ isinstance(ispyb_ids := doc.get("ispyb_dcids"), tuple)
54
+ and len(ispyb_ids) > 0
55
+ ):
56
+ ids_and_shape = list(zip(ispyb_ids, scan_points, strict=False))
57
+ start_frame = 0
58
+ self.zocalo_info = []
59
+ for idx, id_and_shape in enumerate(ids_and_shape):
60
+ id, shape = id_and_shape
61
+ num_frames = number_of_frames_from_scan_spec(shape)
62
+ self.zocalo_info.append(
63
+ ZocaloStartInfo(id, None, start_frame, num_frames, idx)
64
+ )
65
+ start_frame += num_frames
66
+ else:
67
+ raise ISPyBDepositionNotMade(
68
+ f"No ISPyB IDs received by the start of {self.triggering_plan=}"
69
+ )
70
+
71
+ def descriptor(self, doc: EventDescriptor):
72
+ self.descriptors[doc["uid"]] = doc
73
+
74
+ def event(self, doc: Event) -> Event:
75
+ event_descriptor = self.descriptors[doc["descriptor"]]
76
+ if event_descriptor.get("name") == CONST.DESCRIPTORS.ZOCALO_HW_READ:
77
+ filename = doc["data"]["eiger_odin_file_writer_id"]
78
+ for start_info in self.zocalo_info:
79
+ start_info.filename = filename
80
+ assert self.zocalo_interactor is not None
81
+ self.zocalo_interactor.run_start(start_info)
82
+ return doc
83
+
84
+ def stop(self, doc: RunStop):
85
+ if doc.get("run_start") == self.run_uid:
86
+ ISPYB_LOGGER.info(
87
+ f"Zocalo handler received stop document, for run {doc.get('run_start')}."
88
+ )
89
+ assert self.zocalo_interactor is not None
90
+ for info in self.zocalo_info:
91
+ self.zocalo_interactor.run_end(info.ispyb_dcid)
92
+ self._reset_state()
@@ -0,0 +1,35 @@
1
+ from daq_config_server.client import ConfigServer
2
+ from pydantic import BaseModel
3
+
4
+ from mx_bluesky.hyperion.log import LOGGER
5
+ from mx_bluesky.hyperion.parameters.constants import CONST
6
+
7
+ _CONFIG_SERVER: ConfigServer | None = None
8
+
9
+
10
+ def config_server() -> ConfigServer:
11
+ global _CONFIG_SERVER
12
+ if _CONFIG_SERVER is None:
13
+ _CONFIG_SERVER = ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
14
+ return _CONFIG_SERVER
15
+
16
+
17
+ class FeatureFlags(BaseModel):
18
+ # The default value will be used as the fallback when doing a best-effort fetch
19
+ # from the service
20
+ use_panda_for_gridscan: bool = False
21
+ use_gpu_for_gridscan: bool = False
22
+ set_stub_offsets: bool = False
23
+
24
+ @classmethod
25
+ def _get_flags(cls):
26
+ flags = config_server().best_effort_get_all_feature_flags()
27
+ return {f: flags[f] for f in flags if f in cls.__fields__.keys()}
28
+
29
+ @classmethod
30
+ def best_effort(cls):
31
+ return cls(**cls._get_flags())
32
+
33
+ def update_self_from_server(self):
34
+ for flag, value in self._get_flags().items():
35
+ setattr(self, flag, value)
@@ -0,0 +1,13 @@
1
+ from mx_bluesky.hyperion.exceptions import WarningException
2
+
3
+
4
+ class ISPyBDepositionNotMade(Exception):
5
+ """Raised when the ISPyB or Zocalo callbacks can't access ISPyB deposition numbers."""
6
+
7
+ pass
8
+
9
+
10
+ class NoCentreFoundException(WarningException):
11
+ """Error for if zocalo is unable to find the centre during a gridscan."""
12
+
13
+ pass
@@ -0,0 +1,95 @@
1
+ from dataclasses import asdict, dataclass
2
+ from enum import Enum
3
+
4
+
5
+ class Orientation(Enum):
6
+ HORIZONTAL = "horizontal"
7
+ VERTICAL = "vertical"
8
+
9
+
10
+ @dataclass()
11
+ class DataCollectionGroupInfo:
12
+ visit_string: str
13
+ experiment_type: str
14
+ sample_id: int | None
15
+ sample_barcode: str | None = None
16
+
17
+
18
+ @dataclass(kw_only=True)
19
+ class DataCollectionInfo:
20
+ omega_start: float | None = None
21
+ data_collection_number: int | None = None
22
+ xtal_snapshot1: str | None = None
23
+ xtal_snapshot2: str | None = None
24
+ xtal_snapshot3: str | None = None
25
+ xtal_snapshot4: str | None = None
26
+
27
+ n_images: int | None = None
28
+ axis_range: float | None = None
29
+ axis_end: float | None = None
30
+ kappa_start: float | None = None
31
+
32
+ parent_id: int | None = None
33
+ visit_string: str | None = None
34
+ sample_id: int | None = None
35
+ detector_id: int | None = None
36
+ axis_start: float | None = None
37
+ focal_spot_size_at_samplex: float | None = None
38
+ focal_spot_size_at_sampley: float | None = None
39
+ slitgap_vertical: float | None = None
40
+ slitgap_horizontal: float | None = None
41
+ beamsize_at_samplex: float | None = None
42
+ beamsize_at_sampley: float | None = None
43
+ transmission: float | None = None
44
+ comments: str | None = None
45
+ detector_distance: float | None = None
46
+ exp_time: float | None = None
47
+ imgdir: str | None = None
48
+ file_template: str | None = None
49
+ imgprefix: str | None = None
50
+ imgsuffix: str | None = None
51
+ n_passes: int | None = None
52
+ overlap: int | None = None
53
+ flux: float | None = None
54
+ start_image_number: int | None = None
55
+ resolution: float | None = None
56
+ wavelength: float | None = None
57
+ xbeam: float | None = None
58
+ ybeam: float | None = None
59
+ synchrotron_mode: str | None = None
60
+ undulator_gap1: float | None = None
61
+ start_time: str | None = None
62
+
63
+
64
+ @dataclass
65
+ class DataCollectionPositionInfo:
66
+ pos_x: float
67
+ pos_y: float
68
+ pos_z: float
69
+
70
+
71
+ @dataclass
72
+ class DataCollectionGridInfo:
73
+ dx_in_mm: float
74
+ dy_in_mm: float
75
+ steps_x: int
76
+ steps_y: int
77
+ microns_per_pixel_x: float
78
+ microns_per_pixel_y: float
79
+ snapshot_offset_x_pixel: int
80
+ snapshot_offset_y_pixel: int
81
+ orientation: Orientation
82
+ snaked: bool
83
+
84
+ def as_dict(self):
85
+ d = asdict(self)
86
+ d["orientation"] = self.orientation.value
87
+ return d
88
+
89
+
90
+ @dataclass(kw_only=True)
91
+ class ScanDataInfo:
92
+ data_collection_info: DataCollectionInfo
93
+ data_collection_id: int | None = None
94
+ data_collection_position_info: DataCollectionPositionInfo | None = None
95
+ data_collection_grid_info: DataCollectionGridInfo | None = None
@@ -0,0 +1,125 @@
1
+ import configparser
2
+
3
+ from requests import patch, post
4
+ from requests.auth import AuthBase
5
+
6
+ from mx_bluesky.hyperion.external_interaction.exceptions import ISPyBDepositionNotMade
7
+ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
8
+ get_current_time_string,
9
+ get_ispyb_config,
10
+ )
11
+
12
+ RobotActionID = int
13
+
14
+
15
+ class BearerAuth(AuthBase):
16
+ def __init__(self, token):
17
+ self.token = token
18
+
19
+ def __call__(self, r):
20
+ r.headers["authorization"] = "Bearer " + self.token
21
+ return r
22
+
23
+
24
+ def _get_base_url_and_token() -> tuple[str, str]:
25
+ config = configparser.ConfigParser()
26
+ conf = get_ispyb_config()
27
+ config.read(conf)
28
+ expeye_config = config["expeye"]
29
+ return expeye_config["url"], expeye_config["token"]
30
+
31
+
32
+ class ExpeyeInteraction:
33
+ CREATE_ROBOT_ACTION = "/proposals/{proposal}/sessions/{visit_number}/robot-actions"
34
+ UPDATE_ROBOT_ACTION = "/robot-actions/{action_id}"
35
+
36
+ def __init__(self) -> None:
37
+ url, token = _get_base_url_and_token()
38
+ self.base_url = url + "/core"
39
+ self.auth = BearerAuth(token)
40
+
41
+ def _send_and_get_response(self, url, data, send_func) -> dict:
42
+ response = send_func(url, auth=self.auth, json=data)
43
+ if not response.ok:
44
+ raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
45
+ return response.json()
46
+
47
+ def start_load(
48
+ self,
49
+ proposal_reference: str,
50
+ visit_number: int,
51
+ sample_id: int,
52
+ dewar_location: int,
53
+ container_location: int,
54
+ ) -> RobotActionID:
55
+ """Create a robot load entry in ispyb.
56
+
57
+ Args:
58
+ proposal_reference (str): The proposal of the experiment e.g. cm37235
59
+ visit_number (int): The visit number for the proposal, usually this can be
60
+ found added to the end of the proposal e.g. the data for
61
+ visit number 2 of proposal cm37235 is in cm37235-2
62
+ sample_id (int): The id of the sample in the database
63
+ dewar_location (int): Which puck in the dewar the sample is in
64
+ container_location (int): Which pin in that puck has the sample
65
+
66
+ Returns:
67
+ RobotActionID: The id of the robot load action that is created
68
+ """
69
+ url = self.base_url + self.CREATE_ROBOT_ACTION.format(
70
+ proposal=proposal_reference, visit_number=visit_number
71
+ )
72
+
73
+ data = {
74
+ "startTimestamp": get_current_time_string(),
75
+ "sampleId": sample_id,
76
+ "actionType": "LOAD",
77
+ "containerLocation": container_location,
78
+ "dewarLocation": dewar_location,
79
+ }
80
+ response = self._send_and_get_response(url, data, post)
81
+ return response["robotActionId"]
82
+
83
+ def update_barcode_and_snapshots(
84
+ self,
85
+ action_id: RobotActionID,
86
+ barcode: str,
87
+ snapshot_before_path: str,
88
+ snapshot_after_path: str,
89
+ ):
90
+ """Update the barcode and snapshots of an existing robot action.
91
+
92
+ Args:
93
+ action_id (RobotActionID): The id of the action to update
94
+ barcode (str): The barcode to give the action
95
+ snapshot_before_path (str): Path to the snapshot before robot load
96
+ snapshot_after_path (str): Path to the snapshot after robot load
97
+ """
98
+ url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
99
+
100
+ data = {
101
+ "sampleBarcode": barcode,
102
+ "xtalSnapshotBefore": snapshot_before_path,
103
+ "xtalSnapshotAfter": snapshot_after_path,
104
+ }
105
+ self._send_and_get_response(url, data, patch)
106
+
107
+ def end_load(self, action_id: RobotActionID, status: str, reason: str):
108
+ """Finish an existing robot action, providing final information about how it went
109
+
110
+ Args:
111
+ action_id (RobotActionID): The action to finish.
112
+ status (str): The status of the action at the end, "success" for success,
113
+ otherwise error
114
+ reason (str): If the status is in error than the reason for that error
115
+ """
116
+ url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
117
+
118
+ run_status = "SUCCESS" if status == "success" else "ERROR"
119
+
120
+ data = {
121
+ "endTimestamp": get_current_time_string(),
122
+ "status": run_status,
123
+ "message": reason,
124
+ }
125
+ self._send_and_get_response(url, data, patch)