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
@@ -0,0 +1,170 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "57c4caad-c26c-45af-9089-659c32e96c18",
6
+ "metadata": {},
7
+ "source": [
8
+ "Welcome to MX-Bluesky! This introduction is aimed at beamline scientists running from a beamline workstation. To confirm your environment is setup correctly,\n",
9
+ "run the below command to see if you get your expected beamline"
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "id": "9d4cd08b-7097-46c2-9c1a-a71a19681c8d",
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": [
19
+ "from dodal.utils import get_beamline_name\n",
20
+ "\n",
21
+ "beamline = get_beamline_name(\"dev\")\n",
22
+ "beamline = beamline.replace(\"-\",\"_\") # Convert ixx-n to ixx_n as this is the dodal format\n",
23
+ "print(f\"Beamline is {beamline}\")"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "markdown",
28
+ "id": "fe671aa3-a6b5-4a96-b27f-36c3d52453d6",
29
+ "metadata": {},
30
+ "source": [
31
+ "Now let's import your beamline module and see what devices are available"
32
+ ]
33
+ },
34
+ {
35
+ "cell_type": "code",
36
+ "execution_count": 21,
37
+ "id": "535811e1-ac7c-435e-af00-681caefd4787",
38
+ "metadata": {
39
+ "scrolled": true
40
+ },
41
+ "outputs": [
42
+ {
43
+ "name": "stdout",
44
+ "output_type": "stream",
45
+ "text": [
46
+ "synchrotron\n",
47
+ "sample_motors\n"
48
+ ]
49
+ }
50
+ ],
51
+ "source": [
52
+ "import importlib\n",
53
+ "\n",
54
+ "from dodal.utils import collect_factories\n",
55
+ "beamline = \"i02_2\"\n",
56
+ "module_name = f\"dodal.beamlines.{beamline}\"\n",
57
+ "beamline_module = importlib.import_module(module_name)\n",
58
+ "\n",
59
+ "for device in collect_factories(beamline_module):\n",
60
+ " print(device)"
61
+ ]
62
+ },
63
+ {
64
+ "cell_type": "markdown",
65
+ "id": "60768231-ae13-4e64-a1ec-eeec6136514a",
66
+ "metadata": {},
67
+ "source": [
68
+ "The Bluesky RunEngine must be used to interact with these devices. The RunEngine does a lot of things and is quite complicated. Some important benefits:\n",
69
+ "It can protect against using devices incorrectly, it allows us to pause and resume experiments, we can easily trace the sequence of events in experiments,\n",
70
+ "and we can customize it to do useful things like automatic plotting.\n",
71
+ "\n",
72
+ "A Bluesky plan is the name for any experimental procedure. A plan can be anything from a simple motor movement to a complex data collection involving tens of devices. A plan is made up of plan stubs, which are the most simple atomic operations. The two most common plan stubs are called \"abs_set\" and \"rd\", and these are essentially just writing to and reading from the PV of a device. Here's what that would look like on a simulated motor:"
73
+ ]
74
+ },
75
+ {
76
+ "cell_type": "code",
77
+ "execution_count": null,
78
+ "id": "30aaf9a1-3921-4be6-9afa-e81c2b216be8",
79
+ "metadata": {},
80
+ "outputs": [],
81
+ "source": [
82
+ "from bluesky import RunEngine\n",
83
+ "from bluesky import plan_stubs as bps\n",
84
+ "from ophyd_async.sim import SimMotor\n",
85
+ "\n",
86
+ "RE = RunEngine() # Create the RunEngine. We only should be doing this once.\n",
87
+ "fake_motor = SimMotor() # Create the simulated motor\n",
88
+ "\n",
89
+ "# Create the overall plan we want to run in the RunEngine\n",
90
+ "def my_plan():\n",
91
+ " initial_position = yield from bps.rd(fake_motor.user_readback)\n",
92
+ " print(f\"Position of the motor before moving it is {initial_position}\")\n",
93
+ " #Now move the motor\n",
94
+ " yield from bps.abs_set(fake_motor, 5, wait=True) #The code will wait here until the motor has finished moving. The units will be the same as that in EPICS\n",
95
+ " final_position = yield from bps.rd(fake_motor.user_readback)\n",
96
+ " print(f\"Position of the motor after moving it is {final_position}\")\n",
97
+ "\n",
98
+ "# In Bluesky, when we actually run a plan, we must do it through the RunEngine like this\n",
99
+ "RE(my_plan())\n"
100
+ ]
101
+ },
102
+ {
103
+ "cell_type": "markdown",
104
+ "id": "37d4bee0-561c-423a-85a4-ba8f157bc02a",
105
+ "metadata": {},
106
+ "source": [
107
+ "The above code can look confusing, mainly because of the \"yield from\" syntax. We need this because Bluesky plans are python generators rather then regular functions.\n",
108
+ "You don't need to understand generators to use Bluesky, you just need to remember than when calling a plan stub, you need to do \"yield from plan_name()\". If you try and call it like a regular function, eg, just doing \"plan_name()\", this won't actually run, and the code may not error - it catches everyone out!\n",
109
+ " \n",
110
+ "The above code ran instantly because the simulated motor has infinite velocity by default. We could set it to a realistic speed, then the code would take time to complete. Let's set the velocity using abs_set and try again:"
111
+ ]
112
+ },
113
+ {
114
+ "cell_type": "code",
115
+ "execution_count": null,
116
+ "id": "a2b7420b-d319-4ea7-b23f-4f4dad94eb34",
117
+ "metadata": {},
118
+ "outputs": [],
119
+ "source": [
120
+ "from time import time\n",
121
+ "\n",
122
+ "RE(bps.abs_set(fake_motor, 0, wait=True)) # Move motor back to 0\n",
123
+ "RE(bps.abs_set(fake_motor.velocity, 2, wait=True)) #2 units per second\n",
124
+ "start_time = time()\n",
125
+ "RE(my_plan())\n",
126
+ "print(f\"Movement took {round(time()-start_time,2)}s\")"
127
+ ]
128
+ },
129
+ {
130
+ "cell_type": "markdown",
131
+ "id": "897483ec-bb9f-42f3-ad8b-15820bf613ee",
132
+ "metadata": {},
133
+ "source": [
134
+ "Note that the default acceleration of a fake motor is 0.5 units/s/s. To see what a device looks like and what signals are available to it, we recommend navigating the codebase using VSCode. With many devices including motors, the signals\n",
135
+ "of the dodal device will map very closely to the EPICS interface for that device. With a few exceptions, anything you are used to changing using EDM screens will also be doable inside a Bluesky plan.\n",
136
+ "\n",
137
+ "Now try writing a simple Bluesky plan to move a real motor available using the above example code to help."
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "code",
142
+ "execution_count": null,
143
+ "id": "bb69130d-ffc2-428c-83bf-56c4cbd47ca2",
144
+ "metadata": {},
145
+ "outputs": [],
146
+ "source": []
147
+ }
148
+ ],
149
+ "metadata": {
150
+ "kernelspec": {
151
+ "display_name": "Python 3 (ipykernel)",
152
+ "language": "python",
153
+ "name": "python3"
154
+ },
155
+ "language_info": {
156
+ "codemirror_mode": {
157
+ "name": "ipython",
158
+ "version": 3
159
+ },
160
+ "file_extension": ".py",
161
+ "mimetype": "text/x-python",
162
+ "name": "python",
163
+ "nbconvert_exporter": "python",
164
+ "pygments_lexer": "ipython3",
165
+ "version": "3.12.11"
166
+ }
167
+ },
168
+ "nbformat": 4,
169
+ "nbformat_minor": 5
170
+ }
mx_bluesky/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.5.11'
32
- __version_tuple__ = version_tuple = (1, 5, 11)
31
+ __version__ = version = '1.5.14'
32
+ __version_tuple__ = version_tuple = (1, 5, 14)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ import bluesky.plan_stubs as bps
7
+ import bluesky.preprocessors as bpp
8
+ import pydantic
9
+ from dodal.devices.motors import XYZOmegaStage, XYZStage
10
+ from dodal.devices.oav.oav_detector import OAV
11
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
12
+ from dodal.devices.robot import BartRobot, SampleLocation
13
+
14
+ from mx_bluesky.beamlines.aithre_lasershaping.parameters.constants import CONST
15
+ from mx_bluesky.beamlines.aithre_lasershaping.parameters.robot_load_parameters import (
16
+ AithreRobotLoad,
17
+ )
18
+ from mx_bluesky.common.device_setup_plans.robot_load_unload import (
19
+ do_plan_while_lower_gonio_at_home,
20
+ )
21
+ from mx_bluesky.common.experiment_plans.pin_tip_centring_plan import (
22
+ PinTipCentringComposite,
23
+ pin_tip_centre_plan,
24
+ )
25
+ from mx_bluesky.common.parameters.constants import (
26
+ DocDescriptorNames,
27
+ PlanNameConstants,
28
+ )
29
+
30
+
31
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
32
+ class RobotLoadComposite:
33
+ # RobotLoad fields
34
+ robot: BartRobot
35
+ lower_gonio: XYZStage
36
+ oav: OAV
37
+ gonio: XYZOmegaStage
38
+
39
+
40
+ def _move_gonio_to_home_position(
41
+ composite: RobotLoadComposite,
42
+ x_home: float = 0.0,
43
+ y_home: float = 0.0,
44
+ z_home: float = 0.0,
45
+ omega_home: float = 0.0,
46
+ group: str = "group",
47
+ ):
48
+ """
49
+ Move Gonio to home position, default is zero
50
+ """
51
+ yield from bps.abs_set(composite.gonio.omega, omega_home, group=group)
52
+ yield from bps.abs_set(composite.gonio.x, x_home, group=group)
53
+ yield from bps.abs_set(composite.gonio.y, y_home, group=group)
54
+ yield from bps.abs_set(composite.gonio.z, z_home, group=group)
55
+
56
+ yield from bps.wait(group=group)
57
+
58
+
59
+ def _take_robot_snapshots(oav: OAV, directory: Path):
60
+ time_now = datetime.now()
61
+ snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
62
+ for device in [oav.snapshot]:
63
+ yield from bps.abs_set(
64
+ device.filename, snapshot_format.format(device=device.name)
65
+ )
66
+ yield from bps.abs_set(device.directory, str(directory))
67
+ # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
68
+ yield from bps.trigger(device, group="snapshots")
69
+ yield from bps.wait("snapshots")
70
+
71
+
72
+ def _do_robot_load_and_centre(
73
+ composite: RobotLoadComposite,
74
+ sample_location: SampleLocation,
75
+ sample_id: int,
76
+ pin_tip_detection: PinTipDetection,
77
+ tip_offset_microns: float = 0,
78
+ oav_config_file: str = CONST.OAV_CENTRING_FILE,
79
+ ):
80
+ yield from bps.abs_set(composite.robot.next_sample_id, sample_id, wait=True)
81
+ yield from bps.abs_set(
82
+ composite.robot,
83
+ sample_location,
84
+ group="robot_load",
85
+ )
86
+
87
+ yield from _move_gonio_to_home_position(composite=composite, group="robot_load")
88
+
89
+ yield from bps.wait(group="robot_load")
90
+
91
+ pin_tip_centring_composite = PinTipCentringComposite(
92
+ composite.oav, composite.gonio, pin_tip_detection
93
+ )
94
+ yield from pin_tip_centre_plan(
95
+ pin_tip_centring_composite, tip_offset_microns, oav_config_file
96
+ )
97
+
98
+
99
+ def _robot_load_and_snapshots(
100
+ composite: RobotLoadComposite,
101
+ location: SampleLocation,
102
+ snapshot_directory: Path,
103
+ sample_id: int,
104
+ pin_tip_detection: PinTipDetection,
105
+ tip_offset_microns: float = 0,
106
+ oav_config_file: str = CONST.OAV_CENTRING_FILE,
107
+ ):
108
+ yield from bps.create(name=DocDescriptorNames.ROBOT_PRE_LOAD)
109
+ yield from bps.read(composite.robot)
110
+ yield from bps.save()
111
+
112
+ robot_load_plan = _do_robot_load_and_centre(
113
+ composite,
114
+ location,
115
+ sample_id,
116
+ pin_tip_detection,
117
+ tip_offset_microns,
118
+ oav_config_file,
119
+ )
120
+
121
+ gonio_finished = yield from do_plan_while_lower_gonio_at_home(
122
+ robot_load_plan, composite.lower_gonio
123
+ )
124
+ yield from bps.wait(group="snapshot")
125
+
126
+ yield from _take_robot_snapshots(composite.oav, snapshot_directory)
127
+
128
+ yield from bps.create(name=DocDescriptorNames.ROBOT_UPDATE)
129
+ yield from bps.read(composite.robot)
130
+ yield from bps.read(composite.oav.snapshot)
131
+ yield from bps.save()
132
+
133
+ yield from bps.wait(gonio_finished)
134
+
135
+
136
+ def robot_load_and_snapshots_plan(
137
+ composite: RobotLoadComposite,
138
+ params: AithreRobotLoad,
139
+ ptd: PinTipDetection,
140
+ tip_offset_microns: float = 0,
141
+ oav_config_file: str = CONST.OAV_CENTRING_FILE,
142
+ ):
143
+ assert params.sample_puck is not None
144
+ assert params.sample_pin is not None
145
+
146
+ sample_location = SampleLocation(params.sample_puck, params.sample_pin)
147
+
148
+ yield from _move_gonio_to_home_position(composite)
149
+
150
+ yield from bpp.set_run_key_wrapper(
151
+ bpp.run_wrapper(
152
+ _robot_load_and_snapshots(
153
+ composite,
154
+ sample_location,
155
+ params.snapshot_directory,
156
+ params.sample_id,
157
+ ptd,
158
+ tip_offset_microns,
159
+ oav_config_file,
160
+ ),
161
+ md={
162
+ "subplan_name": PlanNameConstants.ROBOT_LOAD,
163
+ "metadata": {"visit": params.visit, "sample_id": params.sample_id},
164
+ "activate_callbacks": [
165
+ "RobotLoadISPyBCallback",
166
+ ],
167
+ },
168
+ ),
169
+ PlanNameConstants.ROBOT_LOAD_AND_SNAPSHOTS,
170
+ )
171
+
172
+
173
+ def robot_unload_plan(
174
+ composite: RobotLoadComposite,
175
+ params: AithreRobotLoad,
176
+ ):
177
+ @bpp.run_decorator(
178
+ md={
179
+ "subplan_name": PlanNameConstants.ROBOT_UNLOAD,
180
+ "metadata": {"visit": params.visit, "sample_id": params.sample_id},
181
+ "activate_callbacks": [
182
+ "RobotLoadISPyBCallback",
183
+ ],
184
+ },
185
+ )
186
+ def do_robot_unload_and_send_to_ispyb():
187
+ yield from _take_robot_snapshots(composite.oav, params.snapshot_directory)
188
+ yield from bps.wait(group="snapshot")
189
+ yield from _move_gonio_to_home_position(composite)
190
+
191
+ yield from bps.abs_set(composite.robot, None, wait=True)
192
+
193
+ yield from bps.create(name=DocDescriptorNames.ROBOT_UPDATE)
194
+ yield from bps.read(composite.robot)
195
+ yield from bps.read(composite.oav.snapshot)
196
+ yield from bps.save()
197
+
198
+ yield from do_robot_unload_and_send_to_ispyb()
@@ -0,0 +1,17 @@
1
+ import os
2
+
3
+ from pydantic.dataclasses import dataclass
4
+
5
+ TEST_MODE = os.environ.get("AITHRE_TEST_MODE")
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class AithreConstants:
10
+ BEAMLINE = "aithre"
11
+ OAV_CENTRING_FILE = (
12
+ "/dls/science/groups/i23/aithre/daq_configuration/json/OAVCentring_aithre.json"
13
+ )
14
+ LOG_FILE_NAME = "aithre.log"
15
+
16
+
17
+ CONST = AithreConstants()
@@ -0,0 +1,13 @@
1
+ from mx_bluesky.common.parameters.components import (
2
+ WithSample,
3
+ WithSnapshot,
4
+ WithVisit,
5
+ )
6
+
7
+
8
+ class AithreRobotLoad(
9
+ WithSample,
10
+ WithSnapshot,
11
+ WithVisit,
12
+ ):
13
+ pass
@@ -0,0 +1,31 @@
1
+ from bluesky.utils import MsgGenerator
2
+ from dodal.common import inject
3
+ from dodal.devices.aithre_lasershaping.goniometer import Goniometer
4
+ from dodal.devices.oav.oav_detector import OAV
5
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
6
+
7
+ from mx_bluesky.beamlines.aithre_lasershaping.parameters.constants import CONST
8
+ from mx_bluesky.common.experiment_plans.pin_tip_centring_plan import (
9
+ PinTipCentringComposite,
10
+ pin_tip_centre_plan,
11
+ )
12
+
13
+
14
+ def aithre_pin_tip_centre(
15
+ oav: OAV = inject("OAV"),
16
+ gonio: Goniometer = inject("gonio"),
17
+ pin_tip_detection: PinTipDetection = inject("pin_tip_detection"),
18
+ tip_offset_microns: float = 0,
19
+ oav_config_file: str = CONST.OAV_CENTRING_FILE,
20
+ ) -> MsgGenerator:
21
+ """
22
+ A plan that use pin_tip_centre_plan from common for aithre
23
+ """
24
+
25
+ composite = PinTipCentringComposite(oav, gonio, pin_tip_detection)
26
+
27
+ yield from pin_tip_centre_plan(
28
+ composite=composite,
29
+ tip_offset_microns=tip_offset_microns,
30
+ oav_config_file=oav_config_file,
31
+ )
@@ -0,0 +1,74 @@
1
+ import datetime
2
+
3
+ from bluesky.utils import MsgGenerator
4
+ from dodal.common import inject
5
+ from dodal.devices.motors import XYZOmegaStage
6
+ from dodal.devices.oav.oav_detector import OAV
7
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
8
+ from dodal.devices.robot import BartRobot
9
+
10
+ from mx_bluesky.beamlines.aithre_lasershaping.experiment_plans.robot_load_plan import (
11
+ RobotLoadComposite,
12
+ robot_load_and_snapshots_plan,
13
+ robot_unload_plan,
14
+ )
15
+ from mx_bluesky.beamlines.aithre_lasershaping.parameters.constants import CONST
16
+ from mx_bluesky.beamlines.aithre_lasershaping.parameters.robot_load_parameters import (
17
+ AithreRobotLoad,
18
+ )
19
+
20
+
21
+ def robot_load_and_snapshot(
22
+ robot: BartRobot = inject("robot"),
23
+ gonio: XYZOmegaStage = inject("gonio"),
24
+ oav: OAV = inject("oav"),
25
+ ptd: PinTipDetection = inject("ptd"),
26
+ tip_offset_microns: float = 0,
27
+ oav_config_file: str = CONST.OAV_CENTRING_FILE,
28
+ sample_puck: int = 0,
29
+ sample_pin: int = 0,
30
+ sample_id: int = 0,
31
+ sample_name: str = "test",
32
+ visit: str = "cm40645-5",
33
+ ) -> MsgGenerator:
34
+ time_now = datetime.datetime.now()
35
+ year_now = str(time_now.year)
36
+ snapshot_directory = f"/dls/i23/data/{year_now}/{visit}/{sample_name}/snapshots"
37
+ composite = RobotLoadComposite(robot, gonio, oav, gonio)
38
+ params = AithreRobotLoad(
39
+ sample_id=sample_id,
40
+ sample_puck=sample_puck,
41
+ sample_pin=sample_pin,
42
+ snapshot_directory=snapshot_directory,
43
+ visit=visit,
44
+ beamline="BL23I",
45
+ )
46
+
47
+ yield from robot_load_and_snapshots_plan(
48
+ composite, params, ptd, tip_offset_microns, oav_config_file
49
+ )
50
+
51
+
52
+ def robot_unload(
53
+ robot: BartRobot = inject("robot"),
54
+ gonio: XYZOmegaStage = inject("gonio"),
55
+ oav: OAV = inject("oav"),
56
+ sample_puck: int = 0,
57
+ sample_pin: int = 0,
58
+ sample_id: int = 0,
59
+ sample_name: str = "test",
60
+ visit: str = "cm40645-5",
61
+ ) -> MsgGenerator:
62
+ time_now = datetime.datetime.now()
63
+ year_now = str(time_now.year)
64
+ snapshot_directory = f"/dls/i23/data/{year_now}/{visit}/{sample_name}/snapshots"
65
+ composite = RobotLoadComposite(robot, gonio, oav, gonio)
66
+ params = AithreRobotLoad(
67
+ sample_id=sample_id,
68
+ sample_puck=sample_puck,
69
+ sample_pin=sample_pin,
70
+ snapshot_directory=snapshot_directory,
71
+ visit=visit,
72
+ beamline="BL23I",
73
+ )
74
+ yield from robot_unload_plan(composite, params)
@@ -1,5 +1,8 @@
1
1
  from mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan import (
2
- i04_grid_detect_then_xray_centre,
2
+ i04_default_grid_detect_and_xray_centre,
3
+ )
4
+ from mx_bluesky.beamlines.i04.oav_centering_plans.oav_imaging import (
5
+ take_oav_image_with_scintillator_in,
3
6
  )
4
7
  from mx_bluesky.beamlines.i04.thawing_plan import (
5
8
  thaw,
@@ -10,6 +13,7 @@ from mx_bluesky.beamlines.i04.thawing_plan import (
10
13
  __all__ = [
11
14
  "thaw",
12
15
  "thaw_and_stream_to_redis",
13
- "i04_grid_detect_then_xray_centre",
16
+ "i04_default_grid_detect_and_xray_centre",
14
17
  "thaw_and_murko_centre",
18
+ "take_oav_image_with_scintillator_in",
15
19
  ]
@@ -8,6 +8,8 @@ from dodal.log import LOGGER
8
8
  from event_model.documents import Event, RunStart, RunStop
9
9
  from redis import StrictRedis
10
10
 
11
+ FORWARDING_COMPLETE_MESSAGE = "image_forwarding_complete"
12
+
11
13
 
12
14
  class OmegaReading(TypedDict):
13
15
  value: float
@@ -56,22 +58,28 @@ class MurkoCallback(CallbackBase):
56
58
  self.previous_omegas: list[OmegaReading] = []
57
59
 
58
60
  def start(self, doc: RunStart) -> RunStart | None:
59
- self.sample_id = doc.get("sample_id")
60
- self.murko_metadata = {
61
- "zoom_percentage": doc.get("zoom_percentage"),
62
- "microns_per_x_pixel": doc.get("microns_per_x_pixel"),
63
- "microns_per_y_pixel": doc.get("microns_per_y_pixel"),
64
- "beam_centre_i": doc.get("beam_centre_i"),
65
- "beam_centre_j": doc.get("beam_centre_j"),
66
- "sample_id": self.sample_id,
67
- }
61
+ self.murko_metadata: dict = {"sample_id": doc.get("sample_id")}
68
62
  self.last_uuid = None
69
63
  self.previous_omegas = []
70
- LOGGER.info(f"Starting to stream metadata to murko under {self.sample_id}")
64
+ LOGGER.info(
65
+ f"Starting to stream metadata to murko under {self.murko_metadata['sample_id']}"
66
+ )
71
67
  return doc
72
68
 
73
69
  def event(self, doc: Event) -> Event:
74
- if latest_omega := doc["data"].get("smargon-omega"):
70
+ data = doc["data"]
71
+ for prefix in ("oav", "oav_full_screen"):
72
+ if f"{prefix}-beam_centre_j" in data:
73
+ self.murko_metadata.update(
74
+ {
75
+ "microns_per_x_pixel": data[f"{prefix}-microns_per_pixel_x"],
76
+ "microns_per_y_pixel": data[f"{prefix}-microns_per_pixel_y"],
77
+ "beam_centre_i": data[f"{prefix}-beam_centre_i"],
78
+ "beam_centre_j": data[f"{prefix}-beam_centre_j"],
79
+ }
80
+ )
81
+
82
+ if (latest_omega := data.get("smargon-omega")) is not None:
75
83
  if len(self.previous_omegas) <= 2 and self.last_uuid:
76
84
  # For the first few images there's not enough data to extrapolate so we
77
85
  # match them one to one
@@ -106,5 +114,12 @@ class MurkoCallback(CallbackBase):
106
114
  self.redis_client.publish("murko", json.dumps(metadata))
107
115
 
108
116
  def stop(self, doc: RunStop) -> RunStop | None:
109
- LOGGER.info(f"Finished streaming {self.sample_id} to murko")
117
+ LOGGER.info(f"Finished streaming {self.murko_metadata['sample_id']} to murko")
118
+ LOGGER.info(
119
+ f"Publishing forwarding complete message: {FORWARDING_COMPLETE_MESSAGE}"
120
+ )
121
+ self.redis_client.publish(
122
+ "murko",
123
+ json.dumps(FORWARDING_COMPLETE_MESSAGE),
124
+ )
110
125
  return doc