mx-bluesky 1.5.10__py3-none-any.whl → 1.5.12__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 (121) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/aithre_lasershaping/experiment_plans/__init__.py +0 -0
  3. mx_bluesky/beamlines/aithre_lasershaping/experiment_plans/robot_load_plan.py +198 -0
  4. mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +2 -2
  5. mx_bluesky/beamlines/aithre_lasershaping/parameters/__init__.py +0 -0
  6. mx_bluesky/beamlines/aithre_lasershaping/parameters/constants.py +17 -0
  7. mx_bluesky/beamlines/aithre_lasershaping/parameters/robot_load_parameters.py +13 -0
  8. mx_bluesky/beamlines/aithre_lasershaping/pin_tip_centring.py +31 -0
  9. mx_bluesky/beamlines/aithre_lasershaping/robot_load.py +80 -0
  10. mx_bluesky/beamlines/i02_1/parameters/gridscan.py +1 -1
  11. mx_bluesky/beamlines/i04/__init__.py +6 -2
  12. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +27 -12
  13. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +94 -20
  14. mx_bluesky/beamlines/i04/external_interaction/__init__.py +0 -0
  15. mx_bluesky/beamlines/i04/external_interaction/config_server.py +15 -0
  16. mx_bluesky/beamlines/i04/oav_centering_plans/__init__.py +0 -0
  17. mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +115 -0
  18. mx_bluesky/beamlines/i04/parameters/__init__.py +0 -0
  19. mx_bluesky/beamlines/i04/parameters/constants.py +21 -0
  20. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +24 -1
  21. mx_bluesky/beamlines/i04/thawing_plan.py +149 -154
  22. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +55 -10
  23. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +1 -1
  24. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +1 -1
  25. mx_bluesky/beamlines/i24/serial/__init__.py +7 -5
  26. mx_bluesky/beamlines/i24/serial/dcid.py +6 -7
  27. mx_bluesky/beamlines/i24/serial/extruder/{i24ssx_Extruder_Collect_py3v2.py → i24ssx_extruder_collect_py3v2.py} +70 -37
  28. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +11 -11
  29. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  30. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +142 -142
  31. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +135 -135
  32. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +8 -8
  33. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +13 -13
  34. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Collect_py3v1.py → i24ssx_chip_collect_py3v1.py} +12 -9
  35. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Manager_py3v1.py → i24ssx_chip_manager_py3v1.py} +81 -78
  36. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_StartUp_py3v1.py → i24ssx_chip_startup_py3v1.py} +3 -3
  37. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +33 -33
  38. mx_bluesky/beamlines/i24/serial/log.py +11 -11
  39. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  40. mx_bluesky/beamlines/i24/serial/parameters/utils.py +5 -5
  41. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -12
  42. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +122 -334
  43. mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +5 -5
  44. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +30 -251
  45. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +3 -3
  46. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +4 -4
  47. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +103 -16
  48. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +64 -0
  49. mx_bluesky/beamlines/i24/serial/write_nexus.py +4 -4
  50. mx_bluesky/common/device_setup_plans/gonio.py +28 -0
  51. mx_bluesky/common/device_setup_plans/manipulate_sample.py +8 -1
  52. mx_bluesky/common/device_setup_plans/robot_load_unload.py +1 -1
  53. mx_bluesky/common/device_setup_plans/setup_oav.py +8 -0
  54. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +0 -5
  55. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +8 -1
  56. mx_bluesky/common/experiment_plans/beamstop_check.py +229 -0
  57. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +8 -6
  58. mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +2 -2
  59. mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +1 -1
  60. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +7 -4
  61. mx_bluesky/common/experiment_plans/inner_plans/write_sample_status.py +2 -2
  62. mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +1 -2
  63. mx_bluesky/{hyperion → common}/experiment_plans/pin_tip_centring_plan.py +23 -24
  64. mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +5 -0
  65. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +13 -15
  66. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +3 -5
  67. mx_bluesky/common/external_interaction/callbacks/common/plan_reactive_callback.py +1 -1
  68. mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +2 -2
  69. mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +3 -3
  70. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +12 -10
  71. mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -2
  72. mx_bluesky/common/external_interaction/config_server.py +4 -4
  73. mx_bluesky/common/external_interaction/ispyb/data_model.py +11 -4
  74. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +163 -4
  75. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +76 -167
  76. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +0 -14
  77. mx_bluesky/common/external_interaction/nexus/nexus_utils.py +2 -2
  78. mx_bluesky/common/external_interaction/nexus/write_nexus.py +3 -3
  79. mx_bluesky/common/parameters/components.py +1 -0
  80. mx_bluesky/common/parameters/constants.py +4 -3
  81. mx_bluesky/common/parameters/device_composites.py +4 -2
  82. mx_bluesky/common/parameters/gridscan.py +2 -2
  83. mx_bluesky/common/utils/exceptions.py +24 -7
  84. mx_bluesky/common/utils/log.py +13 -4
  85. mx_bluesky/common/utils/tracing.py +5 -5
  86. mx_bluesky/common/utils/utils.py +56 -8
  87. mx_bluesky/hyperion/__main__.py +6 -16
  88. mx_bluesky/hyperion/baton_handler.py +38 -14
  89. mx_bluesky/hyperion/device_setup_plans/utils.py +1 -1
  90. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +1 -1
  91. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +15 -13
  92. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +2 -2
  93. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +9 -9
  94. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +7 -8
  95. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +3 -10
  96. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +4 -2
  97. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +10 -4
  98. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  99. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +160 -0
  100. mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -3
  101. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +2 -2
  102. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -3
  103. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -0
  104. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +3 -6
  105. mx_bluesky/hyperion/external_interaction/config_server.py +5 -5
  106. mx_bluesky/hyperion/parameters/constants.py +11 -4
  107. mx_bluesky/hyperion/parameters/device_composites.py +2 -2
  108. mx_bluesky/hyperion/parameters/gridscan.py +4 -4
  109. mx_bluesky/hyperion/parameters/robot_load.py +1 -9
  110. mx_bluesky/hyperion/plan_runner.py +6 -6
  111. mx_bluesky/hyperion/runner.py +10 -8
  112. mx_bluesky/jupyter_example.ipynb +3 -3
  113. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/METADATA +9 -7
  114. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/RECORD +118 -104
  115. mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +0 -65
  116. mx_bluesky/common/external_interaction/callbacks/common/logging_callback.py +0 -29
  117. mx_bluesky/hyperion/device_setup_plans/smargon.py +0 -25
  118. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/WHEEL +0 -0
  119. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/entry_points.txt +0 -0
  120. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/licenses/LICENSE +0 -0
  121. {mx_bluesky-1.5.10.dist-info → mx_bluesky-1.5.12.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ import bluesky.plan_stubs as bps
4
4
  import pydantic
5
5
  from blueapi.core import BlueskyContext
6
6
  from bluesky.utils import Msg
7
- from dodal.devices.backlight import Backlight
7
+ from dodal.devices.motors import XYZOmegaStage
8
8
  from dodal.devices.oav.oav_detector import OAV
9
9
  from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
10
10
  from dodal.devices.oav.pin_image_recognition import PinTipDetection, Tip
@@ -14,27 +14,26 @@ from dodal.devices.oav.utils import (
14
14
  get_move_required_so_that_beam_is_at_pixel,
15
15
  wait_for_tip_to_be_found,
16
16
  )
17
- from dodal.devices.smargon import Smargon
18
17
 
18
+ from mx_bluesky.common.device_setup_plans.gonio import (
19
+ move_gonio_warn_on_out_of_range,
20
+ )
19
21
  from mx_bluesky.common.device_setup_plans.setup_oav import pre_centring_setup_oav
22
+ from mx_bluesky.common.parameters.constants import HardwareConstants
20
23
  from mx_bluesky.common.utils.context import device_composite_from_context
21
- from mx_bluesky.common.utils.exceptions import SampleException, catch_exception_and_warn
24
+ from mx_bluesky.common.utils.exceptions import SampleError, catch_exception_and_warn
22
25
  from mx_bluesky.common.utils.log import LOGGER
23
- from mx_bluesky.hyperion.device_setup_plans.smargon import (
24
- move_smargon_warn_on_out_of_range,
25
- )
26
- from mx_bluesky.hyperion.parameters.constants import CONST
27
26
 
28
27
  DEFAULT_STEP_SIZE = 0.5
28
+ CONST = HardwareConstants()
29
29
 
30
30
 
31
31
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
32
32
  class PinTipCentringComposite:
33
33
  """All devices which are directly or indirectly required by this plan"""
34
34
 
35
- backlight: Backlight
36
35
  oav: OAV
37
- smargon: Smargon
36
+ gonio: XYZOmegaStage
38
37
  pin_tip_detection: PinTipDetection
39
38
 
40
39
 
@@ -53,7 +52,7 @@ def trigger_and_return_pin_tip(
53
52
 
54
53
  def move_pin_into_view(
55
54
  pin_tip_device: PinTipDetection,
56
- smargon: Smargon,
55
+ gonio: XYZOmegaStage,
57
56
  step_magnitude_mm: float = DEFAULT_STEP_SIZE,
58
57
  max_steps: int = 2,
59
58
  ) -> Generator[Msg, None, Pixel]:
@@ -63,13 +62,13 @@ def move_pin_into_view(
63
62
 
64
63
  Args:
65
64
  pin_tip_device (PinTipDetection): The device being used to detect the pin
66
- smargon (Smargon): The gonio to move the tip
65
+ gonio (XYZOmegaStage): The stage(gonio) to move the tip
67
66
  step_magnitude_mm (float, optional): Distance to move the gonio (in mm) for each
68
67
  step of the search. Defaults to 0.5.
69
68
  max_steps (int, optional): The number of steps to search with. Defaults to 2.
70
69
 
71
70
  Raises:
72
- SampleException: Error if the pin tip is never found
71
+ SampleError: Error if the pin tip is never found
73
72
 
74
73
  Returns:
75
74
  Tuple[int, int]: The location of the pin tip in pixels
@@ -88,25 +87,25 @@ def move_pin_into_view(
88
87
  direction_multiple = -1 if tip_xy_px[0] == 0 else 1
89
88
  step_vector_mm = step_magnitude_mm * direction_multiple
90
89
 
91
- smargon_x = yield from bps.rd(smargon.x.user_readback)
92
- ideal_move_to_find_pin = float(smargon_x) + step_vector_mm
93
- high_limit = yield from bps.rd(smargon.x.high_limit_travel)
94
- low_limit = yield from bps.rd(smargon.x.low_limit_travel)
90
+ stage_x = yield from bps.rd(gonio.x.user_readback)
91
+ ideal_move_to_find_pin = float(stage_x) + step_vector_mm
92
+ high_limit = yield from bps.rd(gonio.x.high_limit_travel)
93
+ low_limit = yield from bps.rd(gonio.x.low_limit_travel)
95
94
  move_within_limits = max(min(ideal_move_to_find_pin, high_limit), low_limit)
96
95
  if move_within_limits != ideal_move_to_find_pin:
97
96
  LOGGER.warning(
98
97
  f"Pin tip is off screen, and moving {step_vector_mm}mm would cross limits, "
99
98
  f"moving to {move_within_limits} instead"
100
99
  )
101
- yield from bps.mv(smargon.x, move_within_limits)
100
+ yield from bps.mv(gonio.x, move_within_limits)
102
101
 
103
102
  # Some time for the view to settle after the move
104
- yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
103
+ yield from bps.sleep(CONST.OAV_REFRESH_DELAY)
105
104
 
106
105
  tip_xy_px = yield from trigger_and_return_pin_tip(pin_tip_device)
107
106
 
108
107
  if not pin_tip_valid(tip_xy_px):
109
- raise SampleException(
108
+ raise SampleError(
110
109
  "Pin tip centring failed - pin too long/short/bent and out of range"
111
110
  )
112
111
  else:
@@ -127,7 +126,7 @@ def pin_tip_centre_plan(
127
126
  to be.
128
127
  """
129
128
  oav: OAV = composite.oav
130
- smargon: Smargon = composite.smargon
129
+ gonio: XYZOmegaStage = composite.gonio
131
130
  oav_params = OAVParameters("pinTipCentring", oav_config_file)
132
131
 
133
132
  pin_tip_setup = composite.pin_tip_detection
@@ -139,10 +138,10 @@ def pin_tip_centre_plan(
139
138
  def offset_and_move(tip: Pixel):
140
139
  pixel_to_move_to = (tip[0] + tip_offset_px, tip[1])
141
140
  position_mm = yield from get_move_required_so_that_beam_is_at_pixel(
142
- smargon, pixel_to_move_to, oav
141
+ gonio, pixel_to_move_to, oav
143
142
  )
144
143
  LOGGER.info(f"Tip centring moving to : {position_mm}")
145
- yield from move_smargon_warn_on_out_of_range(smargon, position_mm)
144
+ yield from move_gonio_warn_on_out_of_range(gonio, position_mm)
146
145
 
147
146
  LOGGER.info(f"Tip offset in pixels: {tip_offset_px}")
148
147
 
@@ -152,10 +151,10 @@ def pin_tip_centre_plan(
152
151
 
153
152
  yield from pre_centring_setup_oav(oav, oav_params, pin_tip_setup)
154
153
 
155
- tip = yield from move_pin_into_view(pin_tip_detect, smargon)
154
+ tip = yield from move_pin_into_view(pin_tip_detect, gonio)
156
155
  yield from offset_and_move(tip)
157
156
 
158
- yield from bps.mvr(smargon.omega, -90)
157
+ yield from bps.mvr(gonio.omega, -90)
159
158
 
160
159
  # need to wait for the OAV image to update
161
160
  # See #673 for improvements
@@ -83,12 +83,17 @@ class GridDetectionCallback(CallbackBase):
83
83
  beam_x = data["oav-beam_centre_i"]
84
84
  beam_y = data["oav-beam_centre_j"]
85
85
 
86
+ x_direction = data["oav-x_direction"]
87
+ y_direction = data["oav-y_direction"]
88
+ z_direction = data["oav-z_direction"]
89
+
86
90
  position_grid_start_mm = calculate_x_y_z_of_pixel(
87
91
  current_xyz,
88
92
  smargon_omega,
89
93
  centre_of_first_box,
90
94
  (beam_x, beam_y),
91
95
  (microns_per_pixel_x, microns_per_pixel_y),
96
+ (x_direction, y_direction, z_direction),
92
97
  )
93
98
  LOGGER.info(f"Calculated start position {position_grid_start_mm}")
94
99
 
@@ -5,14 +5,10 @@ from collections.abc import Callable, Sequence
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING, Any, TypeVar, cast
7
7
 
8
- from dodal.beamline_specific_utils.i03 import beam_size_from_aperture
9
8
  from dodal.devices.detector import DetectorParams
10
9
  from dodal.devices.detector.det_resolution import resolution
11
10
  from dodal.devices.synchrotron import SynchrotronMode
12
11
 
13
- from mx_bluesky.common.external_interaction.callbacks.common.logging_callback import (
14
- format_doc_for_log,
15
- )
16
12
  from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
17
13
  PlanReactiveCallback,
18
14
  )
@@ -28,8 +24,12 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
28
24
  from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import get_ispyb_config
29
25
  from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
30
26
  from mx_bluesky.common.parameters.constants import DocDescriptorNames
31
- from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
32
- from mx_bluesky.common.utils.utils import convert_eV_to_angstrom
27
+ from mx_bluesky.common.utils.log import (
28
+ ISPYB_ZOCALO_CALLBACK_LOGGER,
29
+ format_doc_for_log,
30
+ set_dcgid_tag,
31
+ )
32
+ from mx_bluesky.common.utils.utils import convert_ev_to_angstrom
33
33
 
34
34
  D = TypeVar("D")
35
35
  if TYPE_CHECKING:
@@ -43,9 +43,9 @@ def _update_based_on_energy(
43
43
  ):
44
44
  """If energy has been read as part of this reading then add it into the data
45
45
  collection info along with the other fields that depend on it."""
46
- if energy_kev := doc["data"].get("dcm-energy_in_kev", None):
46
+ if energy_kev := doc["data"].get("dcm-energy_in_keV", None):
47
47
  energy_ev = energy_kev * 1000
48
- wavelength_angstroms = convert_eV_to_angstrom(energy_ev)
48
+ wavelength_angstroms = convert_ev_to_angstrom(energy_ev)
49
49
  data_collection_info.wavelength = wavelength_angstroms
50
50
  data_collection_info.resolution = resolution(
51
51
  detector_params,
@@ -147,18 +147,16 @@ class BaseISPyBCallback(PlanReactiveCallback):
147
147
  )
148
148
  return scan_data_infos
149
149
 
150
- def _handle_ispyb_transmission_flux_read(self, doc) -> Sequence[ScanDataInfo]:
150
+ def _handle_ispyb_transmission_flux_read(
151
+ self, doc: Event
152
+ ) -> Sequence[ScanDataInfo]:
151
153
  assert self.params
152
154
  aperture = doc["data"]["aperture_scatterguard-selected_aperture"]
153
- aperture_radius = doc["data"]["aperture_scatterguard-radius"]
154
- beamsize = beam_size_from_aperture(aperture_radius)
155
- beamsize_x_mm = beamsize.x_um / 1000 if beamsize.x_um else None
156
- beamsize_y_mm = beamsize.y_um / 1000 if beamsize.y_um else None
155
+ beamsize_x_mm = doc["data"]["beamsize-x_um"] / 1000
156
+ beamsize_y_mm = doc["data"]["beamsize-y_um"] / 1000
157
157
  hwscan_data_collection_info = DataCollectionInfo(
158
158
  beamsize_at_samplex=beamsize_x_mm,
159
159
  beamsize_at_sampley=beamsize_y_mm,
160
- focal_spot_size_at_samplex=beamsize_x_mm,
161
- focal_spot_size_at_sampley=beamsize_y_mm,
162
160
  flux=doc["data"]["flux-flux_reading"],
163
161
  )
164
162
  if transmission := doc["data"]["attenuator-actual_transmission"]:
@@ -4,15 +4,14 @@ from mx_bluesky.common.external_interaction.ispyb.data_model import (
4
4
  DataCollectionGroupInfo,
5
5
  DataCollectionInfo,
6
6
  )
7
- from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
8
- EIGER_FILE_SUFFIX,
9
- I03_EIGER_DETECTOR,
10
- )
11
7
  from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
12
8
  get_current_time_string,
13
9
  )
14
10
  from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
15
11
 
12
+ I03_EIGER_DETECTOR = 78
13
+ EIGER_FILE_SUFFIX = "h5"
14
+
16
15
 
17
16
  def populate_data_collection_group(params: DiffractionExperimentWithSample):
18
17
  dcg_info = DataCollectionGroupInfo(
@@ -31,7 +30,6 @@ def populate_remaining_data_collection_info(
31
30
  ):
32
31
  data_collection_info.visit_string = params.visit
33
32
  data_collection_info.parent_id = data_collection_group_id
34
- data_collection_info.sample_id = params.sample_id
35
33
  data_collection_info.detector_id = I03_EIGER_DETECTOR
36
34
  data_collection_info.comments = comment
37
35
  data_collection_info.detector_distance = params.detector_params.detector_distance
@@ -40,7 +40,7 @@ class PlanReactiveCallback(CallbackBase):
40
40
  self.log = log
41
41
 
42
42
  def _run_activity_gated(self, name: str, func, doc, override=False):
43
- # Runs `func` if self.active is True or overide is true. Override can be used
43
+ # Runs `func` if self.active is True or override is true. Override can be used
44
44
  # to run the function even after setting self.active to False, i.e. in the last
45
45
  # handler of a run.
46
46
 
@@ -9,7 +9,7 @@ from dodal.devices.zocalo import ZocaloStartInfo, ZocaloTrigger
9
9
  from mx_bluesky.common.parameters.constants import (
10
10
  DocDescriptorNames,
11
11
  )
12
- from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
12
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMadeError
13
13
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
14
14
 
15
15
  if TYPE_CHECKING:
@@ -89,7 +89,7 @@ class ZocaloCallback(CallbackBase):
89
89
  f"Zocalo handler received stop document, for run {doc.get('run_start')}."
90
90
  )
91
91
  if not self._started_zocalo_collections:
92
- raise ISPyBDepositionNotMade(
92
+ raise ISPyBDepositionNotMadeError(
93
93
  f"No ISPyB IDs received by the end of {self.triggering_plan=}"
94
94
  )
95
95
  for info in self._started_zocalo_collections:
@@ -12,7 +12,7 @@ from mx_bluesky.common.external_interaction.ispyb.exp_eye_store import (
12
12
  BLSampleStatus,
13
13
  ExpeyeInteraction,
14
14
  )
15
- from mx_bluesky.common.utils.exceptions import CrystalNotFoundException, SampleException
15
+ from mx_bluesky.common.utils.exceptions import CrystalNotFoundError, SampleError
16
16
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
17
17
 
18
18
 
@@ -47,7 +47,7 @@ class SampleHandlingCallback(PlanReactiveCallback):
47
47
  expeye = ExpeyeInteraction()
48
48
  if doc["exit_status"] != "success":
49
49
  reason = doc.get("reason", "")
50
- exception_type, message = SampleException.type_and_message_from_reason(
50
+ exception_type, message = SampleError.type_and_message_from_reason(
51
51
  reason
52
52
  )
53
53
  self.log.info(
@@ -83,7 +83,7 @@ class SampleHandlingCallback(PlanReactiveCallback):
83
83
 
84
84
  def _decode_sample_status(self, exception_type: str) -> BLSampleStatus:
85
85
  match exception_type:
86
- case SampleException.__name__ | CrystalNotFoundException.__name__:
86
+ case SampleError.__name__ | CrystalNotFoundError.__name__:
87
87
  return BLSampleStatus.ERROR_SAMPLE
88
88
  return BLSampleStatus.ERROR_BEAMLINE
89
89
 
@@ -40,8 +40,8 @@ from mx_bluesky.common.parameters.components import DiffractionExperimentWithSam
40
40
  from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
41
41
  from mx_bluesky.common.parameters.gridscan import GridCommon
42
42
  from mx_bluesky.common.utils.exceptions import (
43
- ISPyBDepositionNotMade,
44
- SampleException,
43
+ ISPyBDepositionNotMadeError,
44
+ SampleError,
45
45
  )
46
46
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
47
47
  from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
@@ -87,7 +87,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
87
87
  To use, subscribe the Bluesky RunEngine to an instance of this class.
88
88
  E.g.:
89
89
  ispyb_handler_callback = FGSISPyBCallback(parameters)
90
- RE.subscribe(ispyb_handler_callback)
90
+ run_engine.subscribe(ispyb_handler_callback)
91
91
  Or decorate a plan using bluesky.preprocessors.subs_decorator.
92
92
 
93
93
  See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
@@ -240,7 +240,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
240
240
  f"{y_steps} by {z_steps}."
241
241
  )
242
242
 
243
- self._populate_axis_info(data_collection_info, omega)
243
+ self._populate_axis_info(data_collection_info, doc["data"])
244
244
 
245
245
  scan_data_info = ScanDataInfo(
246
246
  data_collection_info=data_collection_info,
@@ -254,15 +254,15 @@ class GridscanISPyBCallback(BaseISPyBCallback):
254
254
  self._oav_snapshot_event_idx += 1
255
255
  return [scan_data_info]
256
256
 
257
- def _populate_axis_info(
258
- self, data_collection_info: DataCollectionInfo, omega_start: float | None
259
- ):
260
- if omega_start is not None:
257
+ def _populate_axis_info(self, data_collection_info: DataCollectionInfo, doc: dict):
258
+ if (omega_start := doc.get("smargon-omega")) is not None:
261
259
  omega_in_gda_space = -omega_start
262
260
  data_collection_info.omega_start = omega_in_gda_space
263
261
  data_collection_info.axis_start = omega_in_gda_space
264
262
  data_collection_info.axis_end = omega_in_gda_space
265
263
  data_collection_info.axis_range = 0
264
+ if (chi_start := doc.get("smargon-chi")) is not None:
265
+ data_collection_info.chi_start = chi_start
266
266
 
267
267
  def populate_info_for_update(
268
268
  self,
@@ -303,8 +303,10 @@ class GridscanISPyBCallback(BaseISPyBCallback):
303
303
  f"with uid: {self.uid_to_finalize_on}."
304
304
  )
305
305
  if self.ispyb_ids == IspybIds():
306
- raise ISPyBDepositionNotMade("ispyb was not initialised at run start")
307
- exception_type, message = SampleException.type_and_message_from_reason(
306
+ raise ISPyBDepositionNotMadeError(
307
+ "ispyb was not initialised at run start"
308
+ )
309
+ exception_type, message = SampleError.type_and_message_from_reason(
308
310
  doc.get("reason", "")
309
311
  )
310
312
  if exception_type:
@@ -33,7 +33,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
33
33
  To use, subscribe the Bluesky RunEngine to an instance of this class.
34
34
  E.g.:
35
35
  nexus_file_handler_callback = NexusFileCallback(parameters)
36
- RE.subscribe(nexus_file_handler_callback)
36
+ run_engine.subscribe(nexus_file_handler_callback)
37
37
  Or decorate a plan using bluesky.preprocessors.subs_decorator.
38
38
 
39
39
  See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
@@ -89,7 +89,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
89
89
  nexus_writer.beam,
90
90
  nexus_writer.attenuator,
91
91
  ) = create_beam_and_attenuator_parameters(
92
- data["dcm-energy_in_kev"],
92
+ data["dcm-energy_in_keV"],
93
93
  data["flux-flux_reading"],
94
94
  data["attenuator-actual_transmission"],
95
95
  )
@@ -9,8 +9,8 @@ from pydantic import TypeAdapter
9
9
 
10
10
  from mx_bluesky.common.parameters.constants import (
11
11
  GDA_DOMAIN_PROPERTIES_PATH,
12
- FeatureSetting,
13
- FeatureSettingources,
12
+ FeatureSettings,
13
+ FeatureSettingSources,
14
14
  OavConstants,
15
15
  )
16
16
  from mx_bluesky.common.utils.log import LOGGER
@@ -19,13 +19,13 @@ FEATURE_FLAG_CACHE_LENGTH_S = 60 * 5
19
19
  # Used by the config server when refreshing its cache
20
20
  _JSON_CONFIG_PATHS = [OavConstants.OAV_CONFIG_JSON]
21
21
 
22
- T = TypeVar("T", bound=FeatureSetting)
22
+ T = TypeVar("T", bound=FeatureSettings)
23
23
 
24
24
 
25
25
  class MXConfigClient(ConfigServer, Generic[T]):
26
26
  def __init__(
27
27
  self,
28
- feature_sources: type[FeatureSettingources],
28
+ feature_sources: type[FeatureSettingSources],
29
29
  feature_dc: type[T],
30
30
  url: str = "https://daq-config.diamond.ac.uk",
31
31
  ):
@@ -1,3 +1,8 @@
1
+ """
2
+ TODO replace these with autogenerated schema classes from the ExpEye API
3
+ https://github.com/DiamondLightSource/mx-bluesky/issues/1419
4
+ """
5
+
1
6
  from dataclasses import asdict, dataclass
2
7
  from enum import Enum
3
8
 
@@ -18,6 +23,9 @@ class DataCollectionGroupInfo:
18
23
 
19
24
  @dataclass(kw_only=True)
20
25
  class DataCollectionInfo:
26
+ # This is used internally to keep track of the DataCollectionGroup ID
27
+ parent_id: int | None = None
28
+
21
29
  omega_start: float | None = None
22
30
  data_collection_number: int | None = None
23
31
  xtal_snapshot1: str | None = None
@@ -28,15 +36,12 @@ class DataCollectionInfo:
28
36
  n_images: int | None = None
29
37
  axis_range: float | None = None
30
38
  axis_end: float | None = None
39
+ chi_start: float | None = None
31
40
  kappa_start: float | None = None
32
41
 
33
- parent_id: int | None = None
34
42
  visit_string: str | None = None
35
- sample_id: int | None = None
36
43
  detector_id: int | None = None
37
44
  axis_start: float | None = None
38
- focal_spot_size_at_samplex: float | None = None
39
- focal_spot_size_at_sampley: float | None = None
40
45
  slitgap_vertical: float | None = None
41
46
  slitgap_horizontal: float | None = None
42
47
  beamsize_at_samplex: float | None = None
@@ -60,6 +65,8 @@ class DataCollectionInfo:
60
65
  synchrotron_mode: str | None = None
61
66
  undulator_gap1: float | None = None
62
67
  start_time: str | None = None
68
+ end_time: str | None = None
69
+ run_status: str | None = None
63
70
 
64
71
 
65
72
  @dataclass
@@ -7,11 +7,17 @@ from event_model.documents import Event
7
7
  from requests import JSONDecodeError, patch, post
8
8
  from requests.auth import AuthBase
9
9
 
10
+ from mx_bluesky.common.external_interaction.ispyb.data_model import (
11
+ DataCollectionGridInfo,
12
+ DataCollectionGroupInfo,
13
+ DataCollectionInfo,
14
+ DataCollectionPositionInfo,
15
+ )
10
16
  from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
11
17
  get_current_time_string,
12
18
  get_ispyb_config,
13
19
  )
14
- from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
20
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMadeError
15
21
 
16
22
  RobotActionID = int
17
23
 
@@ -33,14 +39,16 @@ def _get_base_url_and_token() -> tuple[str, str]:
33
39
  return expeye_config["url"], expeye_config["token"]
34
40
 
35
41
 
36
- def _send_and_get_response(auth, url, data, send_func) -> dict:
37
- response = send_func(url, auth=auth, json=data)
42
+ def _send_and_get_response(auth, url, data, send_func, query_params=None) -> dict:
43
+ response = send_func(url, auth=auth, json=data, params=query_params)
38
44
  if not response.ok:
39
45
  try:
40
46
  resp_txt = str(response.json())
41
47
  except JSONDecodeError:
42
48
  resp_txt = str(response)
43
- raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {resp_txt}")
49
+ raise ISPyBDepositionNotMadeError(
50
+ f"Could not write {data} to {url}: {resp_txt}"
51
+ )
44
52
  return response.json()
45
53
 
46
54
 
@@ -179,3 +187,154 @@ class ExpeyeInteraction:
179
187
  bl_sample_status=response["blSampleStatus"],
180
188
  container_id=response["containerId"],
181
189
  )
190
+
191
+ def create_data_group(
192
+ self, proposal_reference: str, visit_number: int, data: DataCollectionGroupInfo
193
+ ) -> int:
194
+ response = _send_and_get_response(
195
+ self._auth,
196
+ self._base_url + f"/proposals/{proposal_reference}/sessions/"
197
+ f"{visit_number}/data-groups",
198
+ _data_collection_group_info_to_json(data),
199
+ post,
200
+ )
201
+ return response["dataCollectionGroupId"]
202
+
203
+ def update_data_group(self, group_id: int, data: DataCollectionGroupInfo):
204
+ _send_and_get_response(
205
+ self._auth,
206
+ self._base_url + f"/data-groups/{group_id}",
207
+ _data_collection_group_info_to_json(data),
208
+ patch,
209
+ )
210
+
211
+ def create_data_collection(self, group_id: int, data: DataCollectionInfo) -> int:
212
+ response = _send_and_get_response(
213
+ self._auth,
214
+ self._base_url + f"/data-groups/{group_id}/data-collections",
215
+ _data_collection_info_to_json(data),
216
+ post,
217
+ )
218
+ return response["dataCollectionId"]
219
+
220
+ def update_data_collection(
221
+ self,
222
+ data_collection_id: int,
223
+ data: DataCollectionInfo,
224
+ append_comment: bool = False,
225
+ ):
226
+ _send_and_get_response(
227
+ self._auth,
228
+ self._base_url + f"/data-collections/{data_collection_id}",
229
+ _data_collection_info_to_json(data),
230
+ patch,
231
+ {"appendComment": "true"} if append_comment else None,
232
+ )
233
+
234
+ def create_position(
235
+ self, data_collection_id: int, data: DataCollectionPositionInfo
236
+ ):
237
+ _send_and_get_response(
238
+ self._auth,
239
+ self._base_url + f"/data-collections/{data_collection_id}/position",
240
+ _position_info_to_json(data),
241
+ post,
242
+ )
243
+
244
+ def create_grid(self, data_collection_id: int, data: DataCollectionGridInfo) -> int:
245
+ response = _send_and_get_response(
246
+ self._auth,
247
+ self._base_url + f"/data-collections/{data_collection_id}/grids",
248
+ _grid_info_to_json(data),
249
+ post,
250
+ )
251
+ return response["gridInfoId"]
252
+
253
+
254
+ def _none_to_absent(json: dict) -> dict:
255
+ for key in [key for key in json if json[key] is None]:
256
+ del json[key]
257
+ return json
258
+
259
+
260
+ def _data_collection_group_info_to_json(data: DataCollectionGroupInfo) -> dict:
261
+ return _none_to_absent(
262
+ {
263
+ "experimentType": data.experiment_type,
264
+ "sampleId": data.sample_id,
265
+ "actualSampleBarcode": data.sample_barcode,
266
+ "comments": data.comments,
267
+ }
268
+ )
269
+
270
+
271
+ def _data_collection_info_to_json(data: DataCollectionInfo) -> dict:
272
+ return _none_to_absent(
273
+ {
274
+ "omegaStart": data.omega_start,
275
+ "dataCollectionNumber": data.data_collection_number,
276
+ "xtalSnapshotFullPath1": data.xtal_snapshot1,
277
+ "xtalSnapshotFullPath2": data.xtal_snapshot2,
278
+ "xtalSnapshotFullPath3": data.xtal_snapshot3,
279
+ "xtalSnapshotFullPath4": data.xtal_snapshot4,
280
+ "numberOfImages": data.n_images,
281
+ "axisRange": data.axis_range,
282
+ "axisEnd": data.axis_end,
283
+ "chiStart": data.chi_start,
284
+ "kappaStart": data.kappa_start,
285
+ "detectorId": data.detector_id,
286
+ "axisStart": data.axis_start,
287
+ "slitGapVertical": data.slitgap_vertical,
288
+ "slitGapHorizontal": data.slitgap_horizontal,
289
+ "beamSizeAtSampleX": data.beamsize_at_samplex,
290
+ "beamSizeAtSampleY": data.beamsize_at_sampley,
291
+ "transmission": data.transmission,
292
+ "comments": data.comments,
293
+ "detectorDistance": data.detector_distance,
294
+ "exposureTime": data.exp_time,
295
+ "imageDirectory": data.imgdir,
296
+ "fileTemplate": data.file_template,
297
+ "imagePrefix": data.imgprefix,
298
+ "imageSuffix": data.imgsuffix,
299
+ "numberOfPasses": data.n_passes,
300
+ "overlap": data.overlap,
301
+ "flux": data.flux,
302
+ "startImageNumber": data.start_image_number,
303
+ "resolution": data.resolution,
304
+ "wavelength": data.wavelength,
305
+ "xBeam": data.xbeam,
306
+ "yBeam": data.ybeam,
307
+ "synchrotronMode": data.synchrotron_mode,
308
+ "undulatorGap1": data.undulator_gap1,
309
+ "startTime": data.start_time,
310
+ "endTime": data.end_time,
311
+ "runStatus": data.run_status,
312
+ }
313
+ )
314
+
315
+
316
+ def _position_info_to_json(data: DataCollectionPositionInfo) -> dict:
317
+ return _none_to_absent(
318
+ {
319
+ "posX": data.pos_x,
320
+ "posY": data.pos_y,
321
+ "posZ": data.pos_z,
322
+ }
323
+ )
324
+
325
+
326
+ def _grid_info_to_json(data: DataCollectionGridInfo) -> dict:
327
+ return _none_to_absent(
328
+ {
329
+ "snapshotOffsetXPixel": data.snapshot_offset_x_pixel,
330
+ "snapshotOffsetYPixel": data.snapshot_offset_y_pixel,
331
+ "dx": data.dx_in_mm,
332
+ "dy": data.dy_in_mm,
333
+ "stepsX": data.steps_x,
334
+ "stepsY": data.steps_y,
335
+ "orientation": data.orientation.value,
336
+ "pixelsPerMicronX": 1 / data.microns_per_pixel_x,
337
+ "pixelsPerMicronY": 1 / data.microns_per_pixel_y,
338
+ "snaked": data.snaked,
339
+ }
340
+ )