mx-bluesky 1.5.11__py3-none-any.whl → 1.5.14__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 (92) hide show
  1. mx_bluesky/Getting started.ipynb +170 -0
  2. mx_bluesky/_version.py +2 -2
  3. mx_bluesky/beamlines/aithre_lasershaping/experiment_plans/__init__.py +0 -0
  4. mx_bluesky/beamlines/aithre_lasershaping/experiment_plans/robot_load_plan.py +198 -0
  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 +74 -0
  10. mx_bluesky/beamlines/i04/__init__.py +6 -2
  11. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +27 -12
  12. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +88 -13
  13. mx_bluesky/beamlines/i04/external_interaction/__init__.py +0 -0
  14. mx_bluesky/beamlines/i04/external_interaction/config_server.py +15 -0
  15. mx_bluesky/beamlines/i04/oav_centering_plans/__init__.py +0 -0
  16. mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +115 -0
  17. mx_bluesky/beamlines/i04/parameters/__init__.py +0 -0
  18. mx_bluesky/beamlines/i04/parameters/constants.py +21 -0
  19. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +24 -1
  20. mx_bluesky/beamlines/i04/thawing_plan.py +147 -152
  21. mx_bluesky/beamlines/i24/serial/dcid.py +4 -5
  22. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +5 -2
  23. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +11 -11
  24. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  25. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +142 -142
  26. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +135 -135
  27. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +8 -8
  28. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +13 -13
  29. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_collect_py3v1.py +7 -4
  30. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_manager_py3v1.py +35 -32
  31. mx_bluesky/beamlines/i24/serial/parameters/utils.py +5 -5
  32. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +113 -306
  33. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +8 -2
  34. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -1
  35. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +6 -6
  36. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +64 -0
  37. mx_bluesky/{hyperion/device_setup_plans/smargon.py → common/device_setup_plans/gonio.py} +9 -6
  38. mx_bluesky/common/device_setup_plans/manipulate_sample.py +8 -1
  39. mx_bluesky/common/device_setup_plans/robot_load_unload.py +1 -1
  40. mx_bluesky/common/device_setup_plans/setup_oav.py +8 -0
  41. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +0 -5
  42. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +8 -1
  43. mx_bluesky/common/experiment_plans/beamstop_check.py +229 -0
  44. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +2 -0
  45. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +5 -2
  46. mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +0 -1
  47. mx_bluesky/{hyperion → common}/experiment_plans/pin_tip_centring_plan.py +20 -21
  48. mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +5 -0
  49. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +10 -12
  50. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +3 -5
  51. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +5 -5
  52. mx_bluesky/common/external_interaction/config_server.py +2 -2
  53. mx_bluesky/common/external_interaction/ispyb/data_model.py +11 -4
  54. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +159 -2
  55. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +76 -166
  56. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +0 -14
  57. mx_bluesky/common/parameters/components.py +1 -0
  58. mx_bluesky/common/parameters/constants.py +5 -2
  59. mx_bluesky/common/parameters/device_composites.py +4 -2
  60. mx_bluesky/common/utils/exceptions.py +15 -0
  61. mx_bluesky/common/utils/log.py +9 -0
  62. mx_bluesky/common/utils/utils.py +48 -0
  63. mx_bluesky/hyperion/__main__.py +3 -13
  64. mx_bluesky/hyperion/baton_handler.py +23 -6
  65. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +1 -0
  66. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +5 -6
  67. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +3 -10
  68. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +4 -2
  69. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +8 -2
  70. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  71. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +166 -0
  72. mx_bluesky/hyperion/external_interaction/agamemnon.py +1 -1
  73. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +48 -21
  74. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +2 -2
  75. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +1 -4
  77. mx_bluesky/hyperion/external_interaction/config_server.py +5 -5
  78. mx_bluesky/hyperion/parameters/constants.py +10 -3
  79. mx_bluesky/hyperion/parameters/device_composites.py +4 -2
  80. mx_bluesky/hyperion/parameters/robot_load.py +1 -9
  81. mx_bluesky/hyperion/plan_runner.py +31 -0
  82. mx_bluesky/hyperion/plan_runner_api.py +14 -1
  83. mx_bluesky/hyperion/utils/context.py +2 -2
  84. mx_bluesky/jupyter_example.ipynb +9 -1
  85. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/METADATA +7 -6
  86. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/RECORD +90 -75
  87. mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +0 -86
  88. mx_bluesky/common/external_interaction/callbacks/common/logging_callback.py +0 -29
  89. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/WHEEL +0 -0
  90. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/entry_points.txt +0 -0
  91. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/licenses/LICENSE +0 -0
  92. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.14.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,12 @@ 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,
@@ -33,8 +39,8 @@ 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())
@@ -181,3 +187,154 @@ class ExpeyeInteraction:
181
187
  bl_sample_status=response["blSampleStatus"],
182
188
  container_id=response["containerId"],
183
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
+ "micronsPerPixelX": data.microns_per_pixel_x,
337
+ "micronsPerPixelY": data.microns_per_pixel_y,
338
+ "snaked": data.snaked,
339
+ }
340
+ )
@@ -1,35 +1,28 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Sequence
4
- from dataclasses import asdict
5
4
  from typing import TYPE_CHECKING
6
5
 
7
- import ispyb
8
- import numpy as np
9
- from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
10
- from ispyb.sp.mxacquisition import MXAcquisition
11
- from ispyb.strictordereddict import StrictOrderedDict
12
6
  from pydantic import BaseModel
13
7
 
8
+ from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping import (
9
+ get_proposal_and_session_from_visit_string,
10
+ )
14
11
  from mx_bluesky.common.external_interaction.ispyb.data_model import (
15
- DataCollectionGridInfo,
16
12
  DataCollectionGroupInfo,
17
13
  DataCollectionInfo,
18
14
  ScanDataInfo,
19
15
  )
16
+ from mx_bluesky.common.external_interaction.ispyb.exp_eye_store import ExpeyeInteraction
20
17
  from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
21
18
  get_current_time_string,
22
- get_session_id_from_visit,
23
19
  )
20
+ from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMadeError
24
21
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
25
- from mx_bluesky.common.utils.tracing import TRACER
26
22
 
27
23
  if TYPE_CHECKING:
28
24
  pass
29
25
 
30
- I03_EIGER_DETECTOR = 78
31
- EIGER_FILE_SUFFIX = "h5"
32
-
33
26
 
34
27
  class IspybIds(BaseModel):
35
28
  data_collection_ids: tuple[int, ...] = ()
@@ -40,6 +33,7 @@ class IspybIds(BaseModel):
40
33
  class StoreInIspyb:
41
34
  def __init__(self, ispyb_config: str) -> None:
42
35
  self.ISPYB_CONFIG_PATH: str = ispyb_config
36
+ self._expeye = ExpeyeInteraction()
43
37
 
44
38
  def begin_deposition(
45
39
  self,
@@ -75,45 +69,41 @@ class StoreInIspyb:
75
69
  data_collection_group_info: DataCollectionGroupInfo | None,
76
70
  scan_data_infos,
77
71
  ) -> IspybIds:
78
- with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
79
- assert conn, "Failed to connect to ISPyB"
80
- if data_collection_group_info:
81
- ispyb_ids.data_collection_group_id = (
82
- self._store_data_collection_group_table(
83
- conn,
84
- data_collection_group_info,
85
- ispyb_ids.data_collection_group_id,
86
- )
87
- )
88
- else:
89
- assert ispyb_ids.data_collection_group_id, (
90
- "Attempt to update data collection without a data collection group ID"
72
+ if data_collection_group_info:
73
+ ispyb_ids.data_collection_group_id = (
74
+ self._store_data_collection_group_table(
75
+ data_collection_group_info, ispyb_ids.data_collection_group_id
91
76
  )
77
+ )
78
+ else:
79
+ assert ispyb_ids.data_collection_group_id, (
80
+ "Attempt to update data collection without a data collection group ID"
81
+ )
92
82
 
93
- grid_ids = list(ispyb_ids.grid_ids)
94
- data_collection_ids_out = list(ispyb_ids.data_collection_ids)
95
- for scan_data_info in scan_data_infos:
96
- data_collection_id = scan_data_info.data_collection_id
97
- if (
98
- scan_data_info.data_collection_info
99
- and not scan_data_info.data_collection_info.parent_id
100
- ):
101
- scan_data_info.data_collection_info.parent_id = (
102
- ispyb_ids.data_collection_group_id
103
- )
104
-
105
- new_data_collection_id, grid_id = self._store_single_scan_data(
106
- conn, scan_data_info, data_collection_id
83
+ grid_ids = list(ispyb_ids.grid_ids)
84
+ data_collection_ids_out = list(ispyb_ids.data_collection_ids)
85
+ for scan_data_info in scan_data_infos:
86
+ data_collection_id = scan_data_info.data_collection_id
87
+ if (
88
+ scan_data_info.data_collection_info
89
+ and not scan_data_info.data_collection_info.parent_id
90
+ ):
91
+ scan_data_info.data_collection_info.parent_id = (
92
+ ispyb_ids.data_collection_group_id
107
93
  )
108
- if not data_collection_id:
109
- data_collection_ids_out.append(new_data_collection_id)
110
- if grid_id:
111
- grid_ids.append(grid_id)
112
- ispyb_ids = IspybIds(
113
- data_collection_ids=tuple(data_collection_ids_out),
114
- grid_ids=tuple(grid_ids),
115
- data_collection_group_id=ispyb_ids.data_collection_group_id,
94
+
95
+ new_data_collection_id, grid_id = self._store_single_scan_data(
96
+ scan_data_info, data_collection_id
116
97
  )
98
+ if not data_collection_id:
99
+ data_collection_ids_out.append(new_data_collection_id)
100
+ if grid_id:
101
+ grid_ids.append(grid_id)
102
+ ispyb_ids = IspybIds(
103
+ data_collection_ids=tuple(data_collection_ids_out),
104
+ grid_ids=tuple(grid_ids),
105
+ data_collection_group_id=ispyb_ids.data_collection_group_id,
106
+ )
117
107
  return ispyb_ids
118
108
 
119
109
  def end_deposition(self, ispyb_ids: IspybIds, success: str, reason: str):
@@ -134,24 +124,19 @@ class StoreInIspyb:
134
124
  run_status = "DataCollection Successful"
135
125
  current_time = get_current_time_string()
136
126
  self._update_scan_with_end_time_and_status(
137
- current_time,
138
- run_status,
139
- reason,
140
- id_,
141
- ispyb_ids.data_collection_group_id,
127
+ current_time, run_status, reason, id_
142
128
  )
143
129
 
144
130
  def append_to_comment(
145
131
  self, data_collection_id: int, comment: str, delimiter: str = " "
146
132
  ) -> None:
147
133
  try:
148
- with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
149
- assert conn is not None, "Failed to connect to ISPyB!"
150
- mx_acquisition: MXAcquisition = conn.mx_acquisition
151
- mx_acquisition.update_data_collection_append_comments(
152
- data_collection_id, comment, delimiter
153
- )
154
- except ispyb.ReadWriteError as e:
134
+ self._expeye.update_data_collection(
135
+ data_collection_id,
136
+ DataCollectionInfo(comments=delimiter + comment),
137
+ True,
138
+ )
139
+ except ISPyBDepositionNotMadeError as e:
155
140
  ISPYB_ZOCALO_CALLBACK_LOGGER.warning(
156
141
  f"Unable to log comment, comment probably exceeded column length: {comment}",
157
142
  exc_info=e,
@@ -162,143 +147,68 @@ class StoreInIspyb:
162
147
  dcg_info: DataCollectionGroupInfo,
163
148
  data_collection_group_id: int | None = None,
164
149
  ) -> None:
165
- with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
166
- assert conn is not None, "Failed to connect to ISPyB!"
167
- self._store_data_collection_group_table(
168
- conn,
169
- dcg_info,
170
- data_collection_group_id,
171
- )
150
+ self._store_data_collection_group_table(dcg_info, data_collection_group_id)
172
151
 
173
152
  def _update_scan_with_end_time_and_status(
174
- self,
175
- end_time: str,
176
- run_status: str,
177
- reason: str,
178
- data_collection_id: int,
179
- data_collection_group_id: int,
153
+ self, end_time: str, run_status: str, reason: str, data_collection_id: int
180
154
  ) -> None:
181
- if reason is not None and reason != "":
155
+ if reason != "":
182
156
  self.append_to_comment(data_collection_id, f"{run_status} reason: {reason}")
183
157
 
184
- with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
185
- assert conn is not None, "Failed to connect to ISPyB!"
186
-
187
- mx_acquisition: MXAcquisition = conn.mx_acquisition
188
-
189
- params = mx_acquisition.get_data_collection_params()
190
- params["id"] = data_collection_id
191
- params["parentid"] = data_collection_group_id
192
- params["endtime"] = end_time
193
- params["run_status"] = run_status
194
- mx_acquisition.upsert_data_collection(list(params.values()))
195
-
196
- def _store_position_table(
197
- self, conn: Connector, dc_pos_info, data_collection_id
198
- ) -> int:
199
- mx_acquisition: MXAcquisition = conn.mx_acquisition
200
-
201
- params = mx_acquisition.get_dc_position_params()
202
- params["id"] = data_collection_id
203
- params |= asdict(dc_pos_info)
204
-
205
- return mx_acquisition.update_dc_position(list(params.values()))
158
+ info = DataCollectionInfo(end_time=end_time, run_status=run_status)
159
+ self._expeye.update_data_collection(data_collection_id, info)
206
160
 
207
161
  def _store_data_collection_group_table(
208
162
  self,
209
- conn: Connector,
210
163
  dcg_info: DataCollectionGroupInfo,
211
164
  data_collection_group_id: int | None = None,
212
165
  ) -> int:
213
- mx_acquisition: MXAcquisition = conn.mx_acquisition
214
-
215
- params = mx_acquisition.get_data_collection_group_params()
216
166
  if data_collection_group_id:
217
- params["id"] = data_collection_group_id
218
- params["parent_id"] = get_session_id_from_visit(conn, dcg_info.visit_string)
219
- params |= {k: v for k, v in asdict(dcg_info).items() if k != "visit_string"}
220
-
221
- return self._upsert_data_collection_group(conn, params)
167
+ self._expeye.update_data_group(data_collection_group_id, dcg_info)
168
+ return data_collection_group_id
169
+ else:
170
+ proposal, session = get_proposal_and_session_from_visit_string(
171
+ dcg_info.visit_string
172
+ )
173
+ return self._expeye.create_data_group(proposal, session, dcg_info)
222
174
 
223
175
  def _store_data_collection_table(
224
- self, conn, data_collection_id, data_collection_info
225
- ):
176
+ self, data_collection_id, data_collection_info: DataCollectionInfo
177
+ ) -> int:
226
178
  if data_collection_id and data_collection_info.comments:
227
179
  self.append_to_comment(
228
180
  data_collection_id, data_collection_info.comments, " "
229
181
  )
230
182
  data_collection_info.comments = None
231
183
 
232
- params = self._fill_common_data_collection_params(
233
- conn, data_collection_id, data_collection_info
234
- )
235
-
236
- return self._upsert_data_collection(conn, params)
184
+ if data_collection_id:
185
+ self._expeye.update_data_collection(
186
+ data_collection_id, data_collection_info
187
+ )
188
+ return data_collection_id
189
+ else:
190
+ assert data_collection_info.parent_id, (
191
+ "Data Collection must have a Data Collection Group"
192
+ )
193
+ return self._expeye.create_data_collection(
194
+ data_collection_info.parent_id, data_collection_info
195
+ )
237
196
 
238
197
  def _store_single_scan_data(
239
- self, conn, scan_data_info, data_collection_id=None
198
+ self, scan_data_info, data_collection_id=None
240
199
  ) -> tuple[int, int | None]:
241
200
  data_collection_id = self._store_data_collection_table(
242
- conn, data_collection_id, scan_data_info.data_collection_info
201
+ data_collection_id, scan_data_info.data_collection_info
243
202
  )
244
203
 
245
204
  if scan_data_info.data_collection_position_info:
246
- self._store_position_table(
247
- conn,
248
- scan_data_info.data_collection_position_info,
249
- data_collection_id,
205
+ self._expeye.create_position(
206
+ data_collection_id, scan_data_info.data_collection_position_info
250
207
  )
251
208
 
252
209
  grid_id = None
253
210
  if scan_data_info.data_collection_grid_info:
254
- grid_id = self._store_grid_info_table(
255
- conn,
256
- data_collection_id,
257
- scan_data_info.data_collection_grid_info,
211
+ grid_id = self._expeye.create_grid(
212
+ data_collection_id, scan_data_info.data_collection_grid_info
258
213
  )
259
214
  return data_collection_id, grid_id
260
-
261
- def _store_grid_info_table(
262
- self,
263
- conn: Connector,
264
- ispyb_data_collection_id: int,
265
- dc_grid_info: DataCollectionGridInfo,
266
- ) -> int:
267
- mx_acquisition: MXAcquisition = conn.mx_acquisition
268
- params = mx_acquisition.get_dc_grid_params()
269
- params |= dc_grid_info.as_dict()
270
- params["parentid"] = ispyb_data_collection_id
271
- return mx_acquisition.upsert_dc_grid(list(params.values()))
272
-
273
- def _fill_common_data_collection_params(
274
- self, conn, data_collection_id, data_collection_info: DataCollectionInfo
275
- ) -> StrictOrderedDict:
276
- mx_acquisition: MXAcquisition = conn.mx_acquisition
277
- params = mx_acquisition.get_data_collection_params()
278
-
279
- if data_collection_id:
280
- params["id"] = data_collection_id
281
- if data_collection_info.visit_string:
282
- # This is only needed for populating the DataCollectionGroup
283
- params["visit_id"] = get_session_id_from_visit(
284
- conn, data_collection_info.visit_string
285
- )
286
- params |= {
287
- k: v.item() if isinstance(v, np.generic) else v # Convert to native types
288
- for k, v in asdict(data_collection_info).items()
289
- if k != "visit_string"
290
- }
291
-
292
- return params
293
-
294
- @staticmethod
295
- @TRACER.start_as_current_span("_upsert_data_collection_group")
296
- def _upsert_data_collection_group(
297
- conn: Connector, params: StrictOrderedDict
298
- ) -> int:
299
- return conn.mx_acquisition.upsert_data_collection_group(list(params.values()))
300
-
301
- @staticmethod
302
- @TRACER.start_as_current_span("_upsert_data_collection")
303
- def _upsert_data_collection(conn: Connector, params: StrictOrderedDict) -> int:
304
- return conn.mx_acquisition.upsert_data_collection(list(params.values()))
@@ -1,12 +1,6 @@
1
- from __future__ import annotations
2
-
3
1
  import datetime
4
2
  import os
5
3
 
6
- from ispyb import NoResult
7
- from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
8
- from ispyb.sp.core import Core
9
-
10
4
 
11
5
  def get_ispyb_config() -> str:
12
6
  ispyb_config = os.environ.get("ISPYB_CONFIG_PATH")
@@ -14,14 +8,6 @@ def get_ispyb_config() -> str:
14
8
  return ispyb_config
15
9
 
16
10
 
17
- def get_session_id_from_visit(conn: Connector, visit: str):
18
- try:
19
- core: Core = conn.core
20
- return core.retrieve_visit_id(visit)
21
- except NoResult as e:
22
- raise NoResult(f"No session ID found in ispyb for visit {visit}") from e
23
-
24
-
25
11
  def get_current_time_string():
26
12
  now = datetime.datetime.now()
27
13
  return now.strftime("%Y-%m-%d %H:%M:%S")
@@ -171,6 +171,7 @@ class DiffractionExperiment(
171
171
  ispyb_experiment_type: IspybExperimentType
172
172
  storage_directory: str
173
173
  use_roi_mode: bool = Field(default=GridscanParamConstants.USE_ROI)
174
+ snapshot_directory: Path = None # type:ignore # filled in on validation
174
175
 
175
176
  @model_validator(mode="before")
176
177
  @classmethod
@@ -40,6 +40,8 @@ def _get_oav_config_json_path():
40
40
  return "tests/test_data/test_OAVCentring.json"
41
41
  elif BEAMLINE == "i03":
42
42
  return f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
43
+ elif BEAMLINE == "aithre":
44
+ return "/dls/science/groups/i23/aithre/daq_configuration/json/OAVCentring_aithre.json"
43
45
  else:
44
46
  return f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring.json"
45
47
 
@@ -85,8 +87,9 @@ class HardwareConstants:
85
87
  OAV_REFRESH_DELAY = 0.3
86
88
  PANDA_FGS_RUN_UP_DEFAULT = 0.17
87
89
  CRYOJET_MARGIN_MM = 0.2
88
- THAWING_TIME = 40
89
90
  TIP_OFFSET_UM = 0
91
+ MAX_CRYO_TEMP_K = 110
92
+ MAX_CRYO_PRESSURE_BAR = 0.1
90
93
 
91
94
  # Value quoted in https://www.dectris.com/en/detectors/x-ray-detectors/eiger2/eiger2-for-synchrotrons/eiger2-x/,
92
95
  # causes dropped frames, so increase value for safety
@@ -163,7 +166,7 @@ class Status(Enum):
163
166
 
164
167
 
165
168
  @dataclass
166
- class FeatureSetting: ... # List of features and their default values. Subclasses must also be a pydantic dataclass
169
+ class FeatureSettings: ... # List of features and their default values. Subclasses must also be a pydantic dataclass
167
170
 
168
171
 
169
172
  class FeatureSettingSources(
@@ -4,6 +4,7 @@ from dodal.devices.aperturescatterguard import (
4
4
  )
5
5
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
6
6
  from dodal.devices.backlight import Backlight
7
+ from dodal.devices.beamsize.beamsize import BeamsizeBase
7
8
  from dodal.devices.common_dcm import DoubleCrystalMonochromator
8
9
  from dodal.devices.detector.detector_motion import DetectorMotion
9
10
  from dodal.devices.eiger import EigerDetector
@@ -18,7 +19,7 @@ from dodal.devices.robot import BartRobot
18
19
  from dodal.devices.s4_slit_gaps import S4SlitGaps
19
20
  from dodal.devices.smargon import Smargon
20
21
  from dodal.devices.synchrotron import Synchrotron
21
- from dodal.devices.undulator import Undulator
22
+ from dodal.devices.undulator import UndulatorInKeV
22
23
  from dodal.devices.xbpm_feedback import XBPMFeedback
23
24
  from dodal.devices.zebra.zebra import Zebra
24
25
  from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
@@ -51,6 +52,7 @@ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
51
52
  attenuator: BinaryFilterAttenuator
52
53
  backlight: Backlight
53
54
  beamstop: Beamstop
55
+ beamsize: BeamsizeBase
54
56
  dcm: DoubleCrystalMonochromator
55
57
  detector_motion: DetectorMotion
56
58
  zebra_fast_grid_scan: ZebraFastGridScanThreeD
@@ -58,7 +60,7 @@ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
58
60
  oav: OAV
59
61
  pin_tip_detection: PinTipDetection
60
62
  s4_slit_gaps: S4SlitGaps
61
- undulator: Undulator
63
+ undulator: UndulatorInKeV
62
64
  xbpm_feedback: XBPMFeedback
63
65
  zebra: Zebra
64
66
  robot: BartRobot
@@ -16,12 +16,27 @@ class WarningError(
16
16
  pass
17
17
 
18
18
 
19
+ class BeamlineCheckFailureError(Exception):
20
+ """
21
+ An error which is raised during a beamline check to indicate that the check did
22
+ not pass.
23
+ """
24
+
25
+ ...
26
+
27
+
19
28
  class ISPyBDepositionNotMadeError(Exception):
20
29
  """Raised when the ISPyB or Zocalo callbacks can't access ISPyB deposition numbers."""
21
30
 
22
31
  pass
23
32
 
24
33
 
34
+ class BeamlineStateError(Exception):
35
+ """Exception raised when the beamline is in the incorrect state"""
36
+
37
+ pass
38
+
39
+
25
40
  class SampleError(WarningError):
26
41
  """An exception which identifies an issue relating to the sample."""
27
42
 
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import logging
2
3
  from logging.handlers import TimedRotatingFileHandler
3
4
  from os import environ
@@ -27,6 +28,14 @@ ALL_LOGGERS = [LOGGER, ISPYB_ZOCALO_CALLBACK_LOGGER, NEXUS_LOGGER]
27
28
  __logger_handlers: DodalLogHandlers | None = None
28
29
 
29
30
 
31
+ def format_doc_for_log(doc):
32
+ class _BestEffortEncoder(json.JSONEncoder):
33
+ def default(self, o):
34
+ return repr(o)
35
+
36
+ return json.dumps(doc, indent=2, cls=_BestEffortEncoder)
37
+
38
+
30
39
  class ExperimentMetadataTagFilter(logging.Filter):
31
40
  """When an instance of this custom filter is added to a logging handler, dc_group_id
32
41
  and run_id will be tagged in that handlers' log messages."""