mx-bluesky 1.4.1a0__py3-none-any.whl → 1.4.3__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 (105) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
  3. mx_bluesky/beamlines/i24/serial/__init__.py +0 -6
  4. mx_bluesky/beamlines/i24/serial/dcid.py +125 -151
  5. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
  6. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +88 -43
  7. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +2 -46
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +85 -122
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +58 -66
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +1 -19
  12. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +11 -2
  13. mx_bluesky/beamlines/i24/serial/parameters/constants.py +16 -2
  14. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +94 -19
  15. mx_bluesky/beamlines/i24/serial/parameters/utils.py +19 -0
  16. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
  17. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +61 -8
  18. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +81 -40
  19. mx_bluesky/beamlines/i24/serial/write_nexus.py +66 -67
  20. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/aperture_change_callback.py +1 -1
  21. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/grid_detection_callback.py +19 -1
  22. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/ispyb_callback_base.py +40 -34
  23. mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/ispyb_mapping.py +4 -4
  24. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/logging_callback.py +1 -1
  25. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/zocalo_callback.py +14 -9
  26. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_callback.py +46 -38
  27. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_mapping.py +2 -2
  28. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/nexus_callback.py +20 -15
  29. mx_bluesky/common/external_interaction/config_server.py +11 -0
  30. mx_bluesky/common/external_interaction/ispyb/__init__.py +0 -0
  31. mx_bluesky/{hyperion → common}/external_interaction/ispyb/data_model.py +2 -0
  32. mx_bluesky/{hyperion → common}/external_interaction/ispyb/exp_eye_store.py +67 -17
  33. mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_store.py +20 -18
  34. mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_utils.py +2 -2
  35. mx_bluesky/common/external_interaction/nexus/__init__.py +0 -0
  36. mx_bluesky/{hyperion → common}/external_interaction/nexus/nexus_utils.py +21 -6
  37. mx_bluesky/{hyperion → common}/external_interaction/nexus/write_nexus.py +5 -5
  38. mx_bluesky/common/external_interaction/test_config_server.py +38 -0
  39. mx_bluesky/common/parameters/components.py +10 -8
  40. mx_bluesky/common/parameters/constants.py +6 -0
  41. mx_bluesky/common/parameters/gridscan.py +102 -53
  42. mx_bluesky/common/plans/do_fgs.py +4 -4
  43. mx_bluesky/{hyperion → common/utils}/exceptions.py +27 -1
  44. mx_bluesky/common/utils/log.py +17 -7
  45. mx_bluesky/hyperion/__main__.py +15 -14
  46. mx_bluesky/hyperion/device_setup_plans/check_beamstop.py +27 -0
  47. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +34 -37
  48. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +7 -7
  49. mx_bluesky/hyperion/device_setup_plans/position_detector.py +1 -1
  50. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +3 -3
  51. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +21 -4
  52. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +62 -36
  53. mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
  54. mx_bluesky/hyperion/device_setup_plans/utils.py +4 -0
  55. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +8 -8
  56. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +28 -17
  57. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +10 -1
  58. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  59. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +54 -58
  60. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +22 -31
  61. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +57 -40
  62. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +3 -3
  63. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -2
  64. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +6 -14
  65. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +12 -11
  66. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +4 -4
  67. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +39 -30
  68. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +36 -18
  69. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +33 -21
  70. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -9
  71. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
  72. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +31 -20
  73. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -30
  74. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +39 -24
  75. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +25 -24
  76. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -1
  77. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +13 -9
  78. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +50 -0
  80. mx_bluesky/hyperion/external_interaction/config_server.py +15 -1
  81. mx_bluesky/hyperion/parameters/components.py +3 -2
  82. mx_bluesky/hyperion/parameters/constants.py +1 -0
  83. mx_bluesky/hyperion/parameters/gridscan.py +56 -89
  84. mx_bluesky/hyperion/parameters/load_centre_collect.py +51 -6
  85. mx_bluesky/hyperion/parameters/robot_load.py +40 -0
  86. mx_bluesky/hyperion/parameters/rotation.py +28 -3
  87. mx_bluesky/hyperion/utils/context.py +1 -1
  88. mx_bluesky/hyperion/utils/validation.py +5 -3
  89. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/METADATA +6 -6
  90. mx_bluesky-1.4.3.dist-info/RECORD +155 -0
  91. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/WHEEL +1 -1
  92. mx_bluesky/common/parameters/robot_load.py +0 -16
  93. mx_bluesky/hyperion/external_interaction/exceptions.py +0 -13
  94. mx_bluesky/hyperion/log.py +0 -15
  95. mx_bluesky-1.4.1a0.dist-info/RECORD +0 -150
  96. /mx_bluesky/{hyperion/external_interaction/callbacks/xray_centre → common/external_interaction}/__init__.py +0 -0
  97. /mx_bluesky/{hyperion/external_interaction/ispyb → common/external_interaction/callbacks/common}/__init__.py +0 -0
  98. /mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/abstract_event.py +0 -0
  99. /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/log_uid_tag_callback.py +0 -0
  100. /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/plan_reactive_callback.py +0 -0
  101. /mx_bluesky/{hyperion/external_interaction/nexus → common/external_interaction/callbacks/xray_centre}/__init__.py +0 -0
  102. /mx_bluesky/{hyperion → common}/utils/utils.py +0 -0
  103. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/LICENSE +0 -0
  104. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/entry_points.txt +0 -0
  105. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/top_level.txt +0 -0
@@ -5,10 +5,13 @@ from typing import TYPE_CHECKING
5
5
  from bluesky.callbacks import CallbackBase
6
6
  from dodal.devices.zocalo import ZocaloStartInfo, ZocaloTrigger
7
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
8
+ from mx_bluesky.common.parameters.constants import (
9
+ DocDescriptorNames,
10
+ TriggerConstants,
11
+ )
12
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
13
+ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
14
+ from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
12
15
 
13
16
  if TYPE_CHECKING:
14
17
  from event_model.documents import Event, EventDescriptor, RunStart, RunStop
@@ -39,11 +42,13 @@ class ZocaloCallback(CallbackBase):
39
42
  self._reset_state()
40
43
 
41
44
  def start(self, doc: RunStart):
42
- ISPYB_LOGGER.info("Zocalo handler received start document.")
43
- if triggering_plan := doc.get(CONST.TRIGGER.ZOCALO):
45
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info("Zocalo handler received start document.")
46
+ if triggering_plan := doc.get(TriggerConstants.ZOCALO):
44
47
  self.triggering_plan = triggering_plan
45
48
  assert isinstance(zocalo_environment := doc.get("zocalo_environment"), str)
46
- ISPYB_LOGGER.info(f"Zocalo environment set to {zocalo_environment}.")
49
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
50
+ f"Zocalo environment set to {zocalo_environment}."
51
+ )
47
52
  self.zocalo_interactor = ZocaloTrigger(zocalo_environment)
48
53
 
49
54
  if self.triggering_plan and doc.get("subplan_name") == self.triggering_plan:
@@ -73,7 +78,7 @@ class ZocaloCallback(CallbackBase):
73
78
 
74
79
  def event(self, doc: Event) -> Event:
75
80
  event_descriptor = self.descriptors[doc["descriptor"]]
76
- if event_descriptor.get("name") == CONST.DESCRIPTORS.ZOCALO_HW_READ:
81
+ if event_descriptor.get("name") == DocDescriptorNames.ZOCALO_HW_READ:
77
82
  filename = doc["data"]["eiger_odin_file_writer_id"]
78
83
  for start_info in self.zocalo_info:
79
84
  start_info.filename = filename
@@ -83,7 +88,7 @@ class ZocaloCallback(CallbackBase):
83
88
 
84
89
  def stop(self, doc: RunStop):
85
90
  if doc.get("run_start") == self.run_uid:
86
- ISPYB_LOGGER.info(
91
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
87
92
  f"Zocalo handler received stop document, for run {doc.get('run_start')}."
88
93
  )
89
94
  assert self.zocalo_interactor is not None
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable, Sequence
4
4
  from time import time
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING, Any, TypeVar
6
6
 
7
7
  import numpy as np
8
8
  from bluesky import preprocessors as bpp
@@ -12,57 +12,61 @@ from dodal.devices.zocalo.zocalo_results import (
12
12
  get_processing_results_from_event,
13
13
  )
14
14
 
15
- from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
16
- from mx_bluesky.common.parameters.constants import PlanNameConstants
17
- from mx_bluesky.common.utils.log import set_dcgid_tag
18
- from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
15
+ from mx_bluesky.common.external_interaction.callbacks.common.ispyb_callback_base import (
16
+ BaseISPyBCallback,
17
+ )
18
+ from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping import (
19
19
  populate_data_collection_group,
20
20
  populate_remaining_data_collection_info,
21
21
  )
22
- from mx_bluesky.hyperion.external_interaction.callbacks.ispyb_callback_base import (
23
- BaseISPyBCallback,
24
- )
25
- from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import (
22
+ from mx_bluesky.common.external_interaction.callbacks.common.logging_callback import (
26
23
  format_doc_for_log,
27
24
  )
28
- from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_mapping import (
25
+ from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping import (
29
26
  construct_comment_for_gridscan,
30
27
  populate_xy_data_collection_info,
31
28
  populate_xz_data_collection_info,
32
29
  )
33
- from mx_bluesky.hyperion.external_interaction.exceptions import ISPyBDepositionNotMade
34
- from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
30
+ from mx_bluesky.common.external_interaction.ispyb.data_model import (
35
31
  DataCollectionGridInfo,
36
32
  DataCollectionInfo,
37
33
  DataCollectionPositionInfo,
38
34
  Orientation,
39
35
  ScanDataInfo,
40
36
  )
41
- from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
37
+ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
42
38
  IspybIds,
43
39
  StoreInIspyb,
44
40
  )
45
- from mx_bluesky.hyperion.log import ISPYB_LOGGER
46
- from mx_bluesky.hyperion.parameters.constants import CONST
47
- from mx_bluesky.hyperion.parameters.gridscan import (
41
+ from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
42
+ from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
43
+ from mx_bluesky.common.parameters.gridscan import (
48
44
  GridCommon,
49
45
  )
46
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
47
+ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
50
48
 
51
49
  if TYPE_CHECKING:
52
50
  from event_model import Event, RunStart, RunStop
53
51
 
54
52
 
55
53
  def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
56
- return bpp.run_wrapper(
57
- plan_generator,
58
- md={
59
- "activate_callbacks": ["GridscanISPyBCallback"],
60
- "subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN,
61
- "hyperion_parameters": parameters.model_dump_json(),
62
- },
54
+ return bpp.set_run_key_wrapper(
55
+ bpp.run_wrapper(
56
+ plan_generator,
57
+ md={
58
+ "activate_callbacks": ["GridscanISPyBCallback"],
59
+ "subplan_name": PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN,
60
+ "mx_bluesky_parameters": parameters.model_dump_json(),
61
+ },
62
+ ),
63
+ PlanNameConstants.ISPYB_ACTIVATION,
63
64
  )
64
65
 
65
66
 
67
+ T = TypeVar("T", bound="GridCommon")
68
+
69
+
66
70
  class GridscanISPyBCallback(BaseISPyBCallback):
67
71
  """Callback class to handle the deposition of experiment parameters into the ISPyB
68
72
  database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
@@ -80,27 +84,29 @@ class GridscanISPyBCallback(BaseISPyBCallback):
80
84
 
81
85
  def __init__(
82
86
  self,
87
+ param_type: type[T],
83
88
  *,
84
89
  emit: Callable[..., Any] | None = None,
85
90
  ) -> None:
86
91
  super().__init__(emit=emit)
87
92
  self.ispyb: StoreInIspyb
88
93
  self.ispyb_ids: IspybIds = IspybIds()
94
+ self.param_type = param_type
89
95
  self._start_of_fgs_uid: str | None = None
90
96
  self._processing_start_time: float | None = None
91
97
 
92
98
  def activity_gated_start(self, doc: RunStart):
93
99
  if doc.get("subplan_name") == PlanNameConstants.DO_FGS:
94
100
  self._start_of_fgs_uid = doc.get("uid")
95
- if doc.get("subplan_name") == CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN:
101
+ if doc.get("subplan_name") == PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN:
96
102
  self.uid_to_finalize_on = doc.get("uid")
97
- ISPYB_LOGGER.info(
103
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
98
104
  "ISPyB callback received start document with experiment parameters and "
99
105
  f"uid: {self.uid_to_finalize_on}"
100
106
  )
101
- hyperion_params = doc.get("hyperion_parameters")
102
- assert isinstance(hyperion_params, str)
103
- self.params = GridCommon.model_validate_json(hyperion_params)
107
+ mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
108
+ assert isinstance(mx_bluesky_parameters, str)
109
+ self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
104
110
  self.ispyb = StoreInIspyb(self.ispyb_config)
105
111
  data_collection_group_info = populate_data_collection_group(self.params)
106
112
 
@@ -137,7 +143,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
137
143
  descriptor_name = self.descriptors[doc["descriptor"]].get("name")
138
144
  if descriptor_name == ZOCALO_READING_PLAN_NAME:
139
145
  self._handle_zocalo_read_event(doc)
140
- elif descriptor_name == CONST.DESCRIPTORS.OAV_GRID_SNAPSHOT_TRIGGERED:
146
+ elif descriptor_name == DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED:
141
147
  scan_data_infos = self._handle_oav_grid_snapshot_triggered(doc)
142
148
  self.ispyb_ids = self.ispyb.update_deposition(
143
149
  self.ispyb_ids, scan_data_infos
@@ -151,7 +157,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
151
157
  proc_time = time() - self._processing_start_time
152
158
  crystal_summary = f"Zocalo processing took {proc_time:.2f} s. "
153
159
  bboxes: list[np.ndarray] = []
154
- ISPYB_LOGGER.info(
160
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
155
161
  f"Amending comment based on Zocalo reading doc: {format_doc_for_log(doc)}"
156
162
  )
157
163
 
@@ -173,9 +179,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
173
179
  )
174
180
  else:
175
181
  crystal_summary += "Zocalo found no crystals in this gridscan."
176
- assert (
177
- self.ispyb_ids.data_collection_ids
178
- ), "No data collection to add results to"
182
+ assert self.ispyb_ids.data_collection_ids, (
183
+ "No data collection to add results to"
184
+ )
179
185
  self.ispyb.append_to_comment(
180
186
  self.ispyb_ids.data_collection_ids[0], crystal_summary
181
187
  )
@@ -222,7 +228,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
222
228
  data_collection_id=data_collection_id,
223
229
  data_collection_grid_info=data_collection_grid_info,
224
230
  )
225
- ISPYB_LOGGER.info("Updating ispyb data collection after oav snapshot.")
231
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
232
+ "Updating ispyb data collection after oav snapshot."
233
+ )
226
234
  self._oav_snapshot_event_idx += 1
227
235
  return [scan_data_info]
228
236
 
@@ -242,9 +250,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
242
250
  event_sourced_position_info: DataCollectionPositionInfo | None,
243
251
  params: DiffractionExperimentWithSample,
244
252
  ) -> Sequence[ScanDataInfo]:
245
- assert (
246
- self.ispyb_ids.data_collection_ids
247
- ), "Expect at least one valid data collection to record scan data"
253
+ assert self.ispyb_ids.data_collection_ids, (
254
+ "Expect at least one valid data collection to record scan data"
255
+ )
248
256
  xy_scan_data_info = ScanDataInfo(
249
257
  data_collection_info=event_sourced_data_collection_info,
250
258
  data_collection_id=self.ispyb_ids.data_collection_ids[0],
@@ -267,7 +275,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
267
275
  if doc.get("run_start") == self._start_of_fgs_uid:
268
276
  self._processing_start_time = time()
269
277
  if doc.get("run_start") == self.uid_to_finalize_on:
270
- ISPYB_LOGGER.info(
278
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
271
279
  "ISPyB callback received stop document corresponding to start document "
272
280
  f"with uid: {self.uid_to_finalize_on}."
273
281
  )
@@ -4,7 +4,7 @@ import numpy
4
4
  from dodal.devices.detector import DetectorParams
5
5
  from dodal.devices.oav import utils as oav_utils
6
6
 
7
- from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
7
+ from mx_bluesky.common.external_interaction.ispyb.data_model import (
8
8
  DataCollectionGridInfo,
9
9
  DataCollectionInfo,
10
10
  )
@@ -43,7 +43,7 @@ def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
43
43
  grid_info.microns_per_pixel_y,
44
44
  )
45
45
  return (
46
- "Hyperion: Xray centring - Diffraction grid scan of "
46
+ "MX-Bluesky: Xray centring - Diffraction grid scan of "
47
47
  f"{grid_info.steps_x} by "
48
48
  f"{grid_info.steps_y} images in "
49
49
  f"{(grid_info.dx_in_mm * 1e3):.1f} um by "
@@ -1,22 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, TypeVar
4
4
 
5
- from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
5
+ from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
6
6
  PlanReactiveCallback,
7
7
  )
8
- from mx_bluesky.hyperion.external_interaction.nexus.nexus_utils import (
8
+ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
9
9
  create_beam_and_attenuator_parameters,
10
10
  vds_type_based_on_bit_depth,
11
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 HyperionThreeDGridScan
12
+ from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
13
+ from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
14
+ from mx_bluesky.common.parameters.gridscan import (
15
+ SpecifiedThreeDGridScan,
16
+ )
17
+ from mx_bluesky.common.utils.log import NEXUS_LOGGER
16
18
 
17
19
  if TYPE_CHECKING:
18
20
  from event_model.documents import Event, EventDescriptor, RunStart
19
21
 
22
+ T = TypeVar("T", bound="SpecifiedThreeDGridScan")
23
+
20
24
 
21
25
  class GridscanNexusFileCallback(PlanReactiveCallback):
22
26
  """Callback class to handle the creation of Nexus files based on experiment \
@@ -35,8 +39,9 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
35
39
  See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
36
40
  """
37
41
 
38
- def __init__(self) -> None:
42
+ def __init__(self, param_type: type[T]) -> None:
39
43
  super().__init__(NEXUS_LOGGER)
44
+ self.param_type = param_type
40
45
  self.run_start_uid: str | None = None
41
46
  self.nexus_writer_1: NexusWriter | None = None
42
47
  self.nexus_writer_2: NexusWriter | None = None
@@ -44,13 +49,13 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
44
49
  self.log = NEXUS_LOGGER
45
50
 
46
51
  def activity_gated_start(self, doc: RunStart):
47
- if doc.get("subplan_name") == CONST.PLAN.GRIDSCAN_OUTER:
48
- hyperion_params = doc.get("hyperion_parameters")
49
- assert isinstance(hyperion_params, str)
52
+ if doc.get("subplan_name") == PlanNameConstants.GRIDSCAN_OUTER:
53
+ mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
54
+ assert isinstance(mx_bluesky_parameters, str)
50
55
  NEXUS_LOGGER.info(
51
- f"Nexus writer received start document with experiment parameters {hyperion_params}"
56
+ f"Nexus writer received start document with experiment parameters {mx_bluesky_parameters}"
52
57
  )
53
- parameters = HyperionThreeDGridScan.model_validate_json(hyperion_params)
58
+ parameters = self.param_type.model_validate_json(mx_bluesky_parameters)
54
59
  d_size = parameters.detector_params.detector_size_constants.det_size_pixels
55
60
  grid_n_img_1 = parameters.scan_indices[1]
56
61
  grid_n_img_2 = parameters.num_images - grid_n_img_1
@@ -75,7 +80,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
75
80
 
76
81
  def activity_gated_event(self, doc: Event) -> Event | None:
77
82
  assert (event_descriptor := self.descriptors.get(doc["descriptor"])) is not None
78
- if event_descriptor.get("name") == CONST.DESCRIPTORS.HARDWARE_READ_DURING:
83
+ if event_descriptor.get("name") == DocDescriptorNames.HARDWARE_READ_DURING:
79
84
  data = doc["data"]
80
85
  for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]:
81
86
  assert nexus_writer, "Nexus callback did not receive start doc"
@@ -84,7 +89,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
84
89
  nexus_writer.attenuator,
85
90
  ) = create_beam_and_attenuator_parameters(
86
91
  data["dcm-energy_in_kev"],
87
- data["flux_flux_reading"],
92
+ data["flux-flux_reading"],
88
93
  data["attenuator-actual_transmission"],
89
94
  )
90
95
  vds_data_type = vds_type_based_on_bit_depth(
@@ -29,8 +29,19 @@ class FeatureFlags(BaseModel, ABC):
29
29
  def mark_overridden_features(cls, values):
30
30
  assert isinstance(values, dict)
31
31
  values["overriden_features"] = values.copy()
32
+ cls._validate_overridden_features(values)
32
33
  return values
33
34
 
35
+ @classmethod
36
+ def _validate_overridden_features(cls, values: dict):
37
+ """Validates overridden features to ensure they are defined in the model fields."""
38
+ defined_fields = cls.model_fields.keys()
39
+ invalid_features = [key for key in values.keys() if key not in defined_fields]
40
+
41
+ if invalid_features:
42
+ message = f"Invalid feature toggle(s) supplied: {invalid_features}. "
43
+ raise ValueError(message)
44
+
34
45
  def _get_flags(self):
35
46
  flags = type(self).get_config_server().best_effort_get_all_feature_flags()
36
47
  return {f: flags[f] for f in flags if f in self.model_fields.keys()}
@@ -70,6 +70,8 @@ class DataCollectionPositionInfo:
70
70
 
71
71
  @dataclass
72
72
  class DataCollectionGridInfo:
73
+ """This information is used by Zocalo gridscan per-image-analysis"""
74
+
73
75
  dx_in_mm: float
74
76
  dy_in_mm: float
75
77
  steps_x: int
@@ -1,13 +1,15 @@
1
1
  import configparser
2
+ from dataclasses import dataclass
3
+ from enum import StrEnum
2
4
 
3
5
  from requests import patch, post
4
6
  from requests.auth import AuthBase
5
7
 
6
- from mx_bluesky.hyperion.external_interaction.exceptions import ISPyBDepositionNotMade
7
- from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
8
+ from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
8
9
  get_current_time_string,
9
10
  get_ispyb_config,
10
11
  )
12
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
11
13
 
12
14
  RobotActionID = int
13
15
 
@@ -29,20 +31,44 @@ def _get_base_url_and_token() -> tuple[str, str]:
29
31
  return expeye_config["url"], expeye_config["token"]
30
32
 
31
33
 
34
+ def _send_and_get_response(auth, url, data, send_func) -> dict:
35
+ response = send_func(url, auth=auth, json=data)
36
+ if not response.ok:
37
+ raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
38
+ return response.json()
39
+
40
+
41
+ @dataclass
42
+ class BLSample:
43
+ container_id: int
44
+ bl_sample_id: int
45
+ bl_sample_status: str | None
46
+
47
+
48
+ class BLSampleStatus(StrEnum):
49
+ # The sample has been loaded
50
+ LOADED = "LOADED"
51
+ # Problem with the sample e.g. pin too long/short
52
+ ERROR_SAMPLE = "ERROR - sample"
53
+ # Any other general error
54
+ ERROR_BEAMLINE = "ERROR - beamline"
55
+
56
+
57
+ assert all(len(value) <= 20 for value in BLSampleStatus), (
58
+ "Column size limit of 20 for BLSampleStatus"
59
+ )
60
+
61
+
32
62
  class ExpeyeInteraction:
63
+ """Exposes functionality from the Expeye core API"""
64
+
33
65
  CREATE_ROBOT_ACTION = "/proposals/{proposal}/sessions/{visit_number}/robot-actions"
34
66
  UPDATE_ROBOT_ACTION = "/robot-actions/{action_id}"
35
67
 
36
68
  def __init__(self) -> None:
37
69
  url, token = _get_base_url_and_token()
38
- self.base_url = url
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()
70
+ self._base_url = url
71
+ self._auth = BearerAuth(token)
46
72
 
47
73
  def start_load(
48
74
  self,
@@ -66,7 +92,7 @@ class ExpeyeInteraction:
66
92
  Returns:
67
93
  RobotActionID: The id of the robot load action that is created
68
94
  """
69
- url = self.base_url + self.CREATE_ROBOT_ACTION.format(
95
+ url = self._base_url + self.CREATE_ROBOT_ACTION.format(
70
96
  proposal=proposal_reference, visit_number=visit_number
71
97
  )
72
98
 
@@ -77,7 +103,7 @@ class ExpeyeInteraction:
77
103
  "containerLocation": container_location,
78
104
  "dewarLocation": dewar_location,
79
105
  }
80
- response = self._send_and_get_response(url, data, post)
106
+ response = _send_and_get_response(self._auth, url, data, post)
81
107
  return response["robotActionId"]
82
108
 
83
109
  def update_barcode_and_snapshots(
@@ -95,14 +121,14 @@ class ExpeyeInteraction:
95
121
  snapshot_before_path (str): Path to the snapshot before robot load
96
122
  snapshot_after_path (str): Path to the snapshot after robot load
97
123
  """
98
- url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
124
+ url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
99
125
 
100
126
  data = {
101
127
  "sampleBarcode": barcode,
102
128
  "xtalSnapshotBefore": snapshot_before_path,
103
129
  "xtalSnapshotAfter": snapshot_after_path,
104
130
  }
105
- self._send_and_get_response(url, data, patch)
131
+ _send_and_get_response(self._auth, url, data, patch)
106
132
 
107
133
  def end_load(self, action_id: RobotActionID, status: str, reason: str):
108
134
  """Finish an existing robot action, providing final information about how it went
@@ -113,13 +139,37 @@ class ExpeyeInteraction:
113
139
  otherwise error
114
140
  reason (str): If the status is in error than the reason for that error
115
141
  """
116
- url = self.base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
142
+ url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
117
143
 
118
144
  run_status = "SUCCESS" if status == "success" else "ERROR"
119
145
 
120
146
  data = {
121
147
  "endTimestamp": get_current_time_string(),
122
148
  "status": run_status,
123
- "message": reason,
149
+ "message": reason[:255] if reason else "",
124
150
  }
125
- self._send_and_get_response(url, data, patch)
151
+ _send_and_get_response(self._auth, url, data, patch)
152
+
153
+ def update_sample_status(
154
+ self, bl_sample_id: int, bl_sample_status: BLSampleStatus
155
+ ) -> BLSample:
156
+ """Update the blSampleStatus of a sample.
157
+ Args:
158
+ bl_sample_id: The sample ID
159
+ bl_sample_status: The sample status
160
+ status_message: An optional message
161
+ Returns:
162
+ The updated sample
163
+ """
164
+ data = {"blSampleStatus": (str(bl_sample_status))}
165
+ response = _send_and_get_response(
166
+ self._auth, self._base_url + f"/samples/{bl_sample_id}", data, patch
167
+ )
168
+ return self._sample_from_json(response)
169
+
170
+ def _sample_from_json(self, response) -> BLSample:
171
+ return BLSample(
172
+ bl_sample_id=response["blSampleId"],
173
+ bl_sample_status=response["blSampleStatus"],
174
+ container_id=response["containerId"],
175
+ )
@@ -11,18 +11,18 @@ from ispyb.sp.mxacquisition import MXAcquisition
11
11
  from ispyb.strictordereddict import StrictOrderedDict
12
12
  from pydantic import BaseModel
13
13
 
14
- from mx_bluesky.common.utils.tracing import TRACER
15
- from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
14
+ from mx_bluesky.common.external_interaction.ispyb.data_model import (
16
15
  DataCollectionGridInfo,
17
16
  DataCollectionGroupInfo,
18
17
  DataCollectionInfo,
19
18
  ScanDataInfo,
20
19
  )
21
- from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
20
+ from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
22
21
  get_current_time_string,
23
22
  get_session_id_from_visit,
24
23
  )
25
- from mx_bluesky.hyperion.log import ISPYB_LOGGER
24
+ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
25
+ from mx_bluesky.common.utils.tracing import TRACER
26
26
 
27
27
  if TYPE_CHECKING:
28
28
  pass
@@ -62,12 +62,12 @@ class StoreInIspyb:
62
62
  ispyb_ids,
63
63
  scan_data_infos: Sequence[ScanDataInfo],
64
64
  ) -> IspybIds:
65
- assert (
66
- ispyb_ids.data_collection_group_id
67
- ), "Attempted to store scan data without a collection group"
68
- assert (
69
- ispyb_ids.data_collection_ids
70
- ), "Attempted to store scan data without a collection"
65
+ assert ispyb_ids.data_collection_group_id, (
66
+ "Attempted to store scan data without a collection group"
67
+ )
68
+ assert ispyb_ids.data_collection_ids, (
69
+ "Attempted to store scan data without a collection"
70
+ )
71
71
  return self._begin_or_update_deposition(ispyb_ids, None, scan_data_infos)
72
72
 
73
73
  def _begin_or_update_deposition(
@@ -87,7 +87,9 @@ class StoreInIspyb:
87
87
  )
88
88
  )
89
89
  else:
90
- assert ispyb_ids.data_collection_group_id, "Attempt to update data collection without a data collection group ID"
90
+ assert ispyb_ids.data_collection_group_id, (
91
+ "Attempt to update data collection without a data collection group ID"
92
+ )
91
93
 
92
94
  grid_ids = list(ispyb_ids.grid_ids)
93
95
  data_collection_ids_out = list(ispyb_ids.data_collection_ids)
@@ -116,15 +118,15 @@ class StoreInIspyb:
116
118
  return ispyb_ids
117
119
 
118
120
  def end_deposition(self, ispyb_ids: IspybIds, success: str, reason: str):
119
- assert (
120
- ispyb_ids.data_collection_ids
121
- ), "Can't end ISPyB deposition, data_collection IDs are missing"
122
- assert (
123
- ispyb_ids.data_collection_group_id is not None
124
- ), "Cannot end ISPyB deposition without data collection group ID"
121
+ assert ispyb_ids.data_collection_ids, (
122
+ "Can't end ISPyB deposition, data_collection IDs are missing"
123
+ )
124
+ assert ispyb_ids.data_collection_group_id is not None, (
125
+ "Cannot end ISPyB deposition without data collection group ID"
126
+ )
125
127
 
126
128
  for id_ in ispyb_ids.data_collection_ids:
127
- ISPYB_LOGGER.info(
129
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
128
130
  f"End ispyb deposition with status '{success}' and reason '{reason}'."
129
131
  )
130
132
  if success == "fail" or success == "abort":
@@ -7,11 +7,11 @@ from ispyb import NoResult
7
7
  from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
8
8
  from ispyb.sp.core import Core
9
9
 
10
- from mx_bluesky.hyperion.parameters.constants import CONST
10
+ from mx_bluesky.common.parameters.constants import SimConstants
11
11
 
12
12
 
13
13
  def get_ispyb_config():
14
- return os.environ.get("ISPYB_CONFIG_PATH", CONST.SIM.ISPYB_CONFIG)
14
+ return os.environ.get("ISPYB_CONFIG_PATH", SimConstants.ISPYB_CONFIG)
15
15
 
16
16
 
17
17
  def get_session_id_from_visit(conn: Connector, visit: str):