mx-bluesky 1.5.11__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 (85) 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/parameters/__init__.py +0 -0
  5. mx_bluesky/beamlines/aithre_lasershaping/parameters/constants.py +17 -0
  6. mx_bluesky/beamlines/aithre_lasershaping/parameters/robot_load_parameters.py +13 -0
  7. mx_bluesky/beamlines/aithre_lasershaping/pin_tip_centring.py +31 -0
  8. mx_bluesky/beamlines/aithre_lasershaping/robot_load.py +80 -0
  9. mx_bluesky/beamlines/i04/__init__.py +6 -2
  10. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +27 -12
  11. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +87 -13
  12. mx_bluesky/beamlines/i04/external_interaction/__init__.py +0 -0
  13. mx_bluesky/beamlines/i04/external_interaction/config_server.py +15 -0
  14. mx_bluesky/beamlines/i04/oav_centering_plans/__init__.py +0 -0
  15. mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +115 -0
  16. mx_bluesky/beamlines/i04/parameters/__init__.py +0 -0
  17. mx_bluesky/beamlines/i04/parameters/constants.py +21 -0
  18. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +24 -1
  19. mx_bluesky/beamlines/i04/thawing_plan.py +147 -152
  20. mx_bluesky/beamlines/i24/serial/dcid.py +4 -5
  21. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +5 -2
  22. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +11 -11
  23. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  24. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +142 -142
  25. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +135 -135
  26. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +8 -8
  27. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +13 -13
  28. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_collect_py3v1.py +7 -4
  29. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_manager_py3v1.py +35 -32
  30. mx_bluesky/beamlines/i24/serial/parameters/utils.py +5 -5
  31. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +113 -306
  32. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +8 -2
  33. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -1
  34. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +1 -1
  35. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +64 -0
  36. mx_bluesky/{hyperion/device_setup_plans/smargon.py → common/device_setup_plans/gonio.py} +9 -6
  37. mx_bluesky/common/device_setup_plans/manipulate_sample.py +8 -1
  38. mx_bluesky/common/device_setup_plans/robot_load_unload.py +1 -1
  39. mx_bluesky/common/device_setup_plans/setup_oav.py +8 -0
  40. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +0 -5
  41. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +8 -1
  42. mx_bluesky/common/experiment_plans/beamstop_check.py +229 -0
  43. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +2 -0
  44. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +5 -2
  45. mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +0 -1
  46. mx_bluesky/{hyperion → common}/experiment_plans/pin_tip_centring_plan.py +20 -21
  47. mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +5 -0
  48. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +10 -12
  49. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +3 -5
  50. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +5 -5
  51. mx_bluesky/common/external_interaction/config_server.py +2 -2
  52. mx_bluesky/common/external_interaction/ispyb/data_model.py +11 -4
  53. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +159 -2
  54. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +76 -166
  55. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +0 -14
  56. mx_bluesky/common/parameters/components.py +1 -0
  57. mx_bluesky/common/parameters/constants.py +3 -2
  58. mx_bluesky/common/parameters/device_composites.py +4 -2
  59. mx_bluesky/common/utils/exceptions.py +15 -0
  60. mx_bluesky/common/utils/log.py +9 -0
  61. mx_bluesky/common/utils/utils.py +48 -0
  62. mx_bluesky/hyperion/__main__.py +3 -13
  63. mx_bluesky/hyperion/baton_handler.py +23 -6
  64. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +5 -6
  65. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +3 -10
  66. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +4 -2
  67. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +8 -2
  68. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  69. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +160 -0
  70. mx_bluesky/hyperion/external_interaction/agamemnon.py +1 -1
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +2 -2
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +1 -4
  74. mx_bluesky/hyperion/external_interaction/config_server.py +5 -5
  75. mx_bluesky/hyperion/parameters/constants.py +10 -3
  76. mx_bluesky/hyperion/parameters/device_composites.py +2 -2
  77. mx_bluesky/hyperion/parameters/robot_load.py +1 -9
  78. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/METADATA +6 -5
  79. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/RECORD +83 -69
  80. mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +0 -86
  81. mx_bluesky/common/external_interaction/callbacks/common/logging_callback.py +0 -29
  82. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/WHEEL +0 -0
  83. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/entry_points.txt +0 -0
  84. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/licenses/LICENSE +0 -0
  85. {mx_bluesky-1.5.11.dist-info → mx_bluesky-1.5.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,115 @@
1
+ import os
2
+ import time
3
+
4
+ import bluesky.plan_stubs as bps
5
+ from bluesky.utils import MsgGenerator
6
+ from dodal.common import inject
7
+ from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
8
+ from dodal.devices.backlight import Backlight
9
+ from dodal.devices.mx_phase1.beamstop import Beamstop, BeamstopPositions
10
+ from dodal.devices.oav.oav_detector import OAV
11
+ from dodal.devices.robot import BartRobot, PinMounted
12
+ from dodal.devices.scintillator import InOut, Scintillator
13
+ from dodal.devices.xbpm_feedback import XBPMFeedback
14
+ from dodal.devices.zebra.zebra_controlled_shutter import (
15
+ ZebraShutter,
16
+ ZebraShutterControl,
17
+ ZebraShutterState,
18
+ )
19
+ from ophyd_async.core import InOut as core_INOUT
20
+
21
+ from mx_bluesky.common.utils.exceptions import BeamlineStateError
22
+
23
+ initial_wait_group = "Wait for scint to move in"
24
+
25
+
26
+ def take_oav_image_with_scintillator_in(
27
+ image_name: str | None = None,
28
+ image_path: str = "dls_sw/i04/software/bluesky/scratch",
29
+ transmission: float = 1,
30
+ attenuator: BinaryFilterAttenuator = inject("attenuator"),
31
+ shutter: ZebraShutter = inject("sample_shutter"),
32
+ oav: OAV = inject("oav"),
33
+ robot: BartRobot = inject("robot"),
34
+ beamstop: Beamstop = inject("beamstop"),
35
+ backlight: Backlight = inject("backlight"),
36
+ scintillator: Scintillator = inject("scintillator"),
37
+ xbpm_feedback: XBPMFeedback = inject("xbpm_feedback"),
38
+ ) -> MsgGenerator:
39
+ """
40
+ Takes an OAV image at specified transmission after necessary checks and preparation steps.
41
+
42
+ Args:
43
+ image_name: Name of the OAV image to be saved
44
+ image_path: Path where the image should be saved
45
+ transmission: Transmission of the beam, takes a value from 0 to 1 where
46
+ 1 lets all the beam through and 0 lets none of the beam through.
47
+ devices: These are the specific ophyd-devices used for the plan, the
48
+ defaults are always correct.
49
+ """
50
+
51
+ yield from _prepare_beamline_for_scintillator_images(
52
+ robot, beamstop, backlight, scintillator, xbpm_feedback, initial_wait_group
53
+ )
54
+
55
+ yield from bps.abs_set(attenuator, transmission, group=initial_wait_group)
56
+
57
+ if image_name is None:
58
+ image_name = f"{time.time_ns()}ATT{transmission * 100}"
59
+
60
+ yield from bps.wait(initial_wait_group)
61
+
62
+ yield from bps.abs_set(shutter.control_mode, ZebraShutterControl.MANUAL, wait=True)
63
+ yield from bps.abs_set(shutter, ZebraShutterState.OPEN, wait=True)
64
+
65
+ take_and_save_oav_image(file_path=image_path, file_name=image_name, oav=oav)
66
+
67
+
68
+ def _prepare_beamline_for_scintillator_images(
69
+ robot: BartRobot,
70
+ beamstop: Beamstop,
71
+ backlight: Backlight,
72
+ scintillator: Scintillator,
73
+ xbpm_feedback: XBPMFeedback,
74
+ group: str,
75
+ ) -> MsgGenerator:
76
+ """
77
+ Prepares the beamline for oav image by making sure the pin is NOT mounted and
78
+ the beam is on (feedback check). Finally, the scintillator is moved in.
79
+ """
80
+ pin_mounted = yield from bps.rd(robot.gonio_pin_sensor)
81
+ if pin_mounted == PinMounted.PIN_MOUNTED:
82
+ raise BeamlineStateError("Pin should not be mounted!")
83
+
84
+ yield from bps.trigger(xbpm_feedback, group=group)
85
+
86
+ yield from bps.abs_set(
87
+ beamstop.selected_pos, BeamstopPositions.DATA_COLLECTION, group=group
88
+ )
89
+
90
+ yield from bps.abs_set(backlight, core_INOUT.OUT, group=group)
91
+
92
+ yield from bps.abs_set(scintillator.selected_pos, InOut.IN, group=group)
93
+
94
+
95
+ def take_and_save_oav_image(
96
+ file_name: str,
97
+ file_path: str,
98
+ oav: OAV,
99
+ ) -> MsgGenerator:
100
+ """
101
+ Plan which takes and saves an OAV image to the specified path.
102
+ Args:
103
+ file_name: Filename specifying the name of the image,
104
+ file_path: Path as a string specifying where the image should be saved,
105
+ oav: The OAV to take the image with
106
+ """
107
+ group = "oav image path setting"
108
+ full_file_path = file_path + "/" + file_name
109
+ if not os.path.exists(full_file_path):
110
+ yield from bps.abs_set(oav.snapshot.filename, file_name, group=group)
111
+ yield from bps.abs_set(oav.snapshot.directory, file_path, group=group)
112
+ yield from bps.wait(group)
113
+ yield from bps.trigger(oav.snapshot, wait=True)
114
+ else:
115
+ raise FileExistsError("OAV image file path already exists")
File without changes
@@ -0,0 +1,21 @@
1
+ from pydantic.dataclasses import dataclass
2
+
3
+ from mx_bluesky.common.parameters.constants import (
4
+ FeatureSettings,
5
+ FeatureSettingSources,
6
+ )
7
+
8
+
9
+ # These currently exist in GDA domain.properties
10
+ class I04FeatureSettingsSources(FeatureSettingSources):
11
+ ASSUMED_WAVELENGTH_IN_A = "gda.px.expttable.default.wavelength"
12
+ XRC_UNSCALED_TRANSMISSION_FRAC = "gda.mx.bluesky.i04.xrc.unscaled_transmission_frac"
13
+ XRC_UNSCALED_EXPOSURE_TIME_S = "gda.mx.bluesky.i04.xrc.unscaled_exposure_time_s"
14
+
15
+
16
+ # Use these defaults if we can't read from the config server
17
+ @dataclass
18
+ class I04FeatureSettings(FeatureSettings):
19
+ ASSUMED_WAVELENGTH_IN_A: float = 0.95373
20
+ XRC_UNSCALED_TRANSMISSION_FRAC: int = 1
21
+ XRC_UNSCALED_EXPOSURE_TIME_S: float = 0.007
@@ -1,21 +1,27 @@
1
1
  import io
2
2
  import json
3
+ import logging
3
4
  import pickle
4
5
  from datetime import timedelta
6
+ from logging import StreamHandler
5
7
  from typing import TypedDict
6
8
 
7
9
  import numpy as np
8
10
  import zmq
9
11
  from dodal.devices.i04.constants import RedisConstants
10
- from dodal.devices.i04.murko_results import MurkoResult
12
+ from dodal.devices.i04.murko_results import RESULTS_COMPLETE_MESSAGE, MurkoResult
11
13
  from numpy.typing import NDArray
12
14
  from PIL import Image
13
15
  from redis import StrictRedis
14
16
 
17
+ from mx_bluesky.beamlines.i04.callbacks.murko_callback import (
18
+ FORWARDING_COMPLETE_MESSAGE,
19
+ )
15
20
  from mx_bluesky.common.utils.log import LOGGER
16
21
 
17
22
  MURKO_ADDRESS = "tcp://i04-murko-prod.diamond.ac.uk:8008"
18
23
 
24
+
19
25
  FullMurkoResults = dict[str, list[MurkoResult]]
20
26
 
21
27
 
@@ -112,6 +118,12 @@ class BatchMurkoForwarder:
112
118
  self.redis_client.expire(redis_key, timedelta(days=7))
113
119
  self.redis_client.publish("murko-results", pickle.dumps(results))
114
120
 
121
+ def send_stop_message_to_redis(self):
122
+ LOGGER.info(f"Publishing results complete message: {RESULTS_COMPLETE_MESSAGE}")
123
+ self.redis_client.publish(
124
+ "murko-results", pickle.dumps(RESULTS_COMPLETE_MESSAGE)
125
+ )
126
+
115
127
  def add(self, sample_id: str, uuid: str, image: NDArray):
116
128
  """Add an image to the batch to send to murko."""
117
129
  image_size = get_image_size(image)
@@ -159,6 +171,13 @@ class RedisListener:
159
171
  if message and message["type"] == "message":
160
172
  data = json.loads(message["data"])
161
173
  LOGGER.info(f"Received from redis: {data}")
174
+ if data == FORWARDING_COMPLETE_MESSAGE:
175
+ LOGGER.info(
176
+ f"Received forwarding complete message: {FORWARDING_COMPLETE_MESSAGE}"
177
+ )
178
+ self.forwarder.flush()
179
+ self.forwarder.send_stop_message_to_redis()
180
+ return
162
181
  uuid = data["uuid"]
163
182
  sample_id = data["sample_id"]
164
183
 
@@ -188,6 +207,10 @@ class RedisListener:
188
207
 
189
208
 
190
209
  def main():
210
+ stream_handler = StreamHandler()
211
+ stream_handler.setLevel(logging.INFO)
212
+ LOGGER.addHandler(stream_handler)
213
+
191
214
  client = RedisListener()
192
215
  client.listen_for_image_data_forever()
193
216
 
@@ -1,18 +1,15 @@
1
- from collections.abc import Callable
2
- from functools import partial
3
-
4
1
  import bluesky.plan_stubs as bps
5
2
  import bluesky.preprocessors as bpp
6
- from bluesky.preprocessors import run_decorator, subs_decorator
3
+ from bluesky.preprocessors import contingency_decorator, run_decorator, subs_decorator
7
4
  from bluesky.utils import MsgGenerator
8
5
  from dodal.common import inject
9
6
  from dodal.devices.i04.constants import RedisConstants
10
7
  from dodal.devices.i04.murko_results import MurkoResultsDevice
11
- from dodal.devices.oav.oav_detector import OAV
12
8
  from dodal.devices.oav.oav_to_redis_forwarder import OAVToRedisForwarder, Source
13
9
  from dodal.devices.robot import BartRobot
14
10
  from dodal.devices.smargon import Smargon
15
11
  from dodal.devices.thawer import OnOff, Thawer
12
+ from dodal.log import LOGGER
16
13
 
17
14
  from mx_bluesky.beamlines.i04.callbacks.murko_callback import MurkoCallback
18
15
 
@@ -29,53 +26,26 @@ def thaw(
29
26
 
30
27
  Args:
31
28
  time_to_thaw (float): Time to thaw for, in seconds.
32
- rotation (float, optional): How much to rotate by whilst thawing, in degrees.
33
- Defaults to 360.
34
- ... devices: These are the specific ophyd-devices used for the plan, the
35
- defaults are always correct.
29
+ rotation (float): How much to rotate by whilst thawing, in degrees.
30
+ thawer (Thawer): The thawing device.
31
+ smargon (Smargon): The smargon used to rotate.
36
32
  """
37
- yield from _thaw(time_to_thaw, rotation, thawer, smargon)
38
-
39
-
40
- def thaw_and_stream_to_redis(
41
- time_to_thaw: float,
42
- rotation: float = 360,
43
- robot: BartRobot = inject("robot"),
44
- thawer: Thawer = inject("thawer"),
45
- smargon: Smargon = inject("smargon"),
46
- oav: OAV = inject("oav_full_screen"),
47
- oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"),
48
- ) -> MsgGenerator:
49
- """Turns on the thawer and rotates the sample by {rotation} degrees to thaw it, then
50
- rotates {rotation} degrees back and turns the thawer off. The speed of the goniometer
51
- is set such that the process takes whole process will take {time_to_thaw} time.
33
+ initial_velocity = yield from bps.rd(smargon.omega.velocity)
34
+ new_velocity = abs(rotation / time_to_thaw) * 2.0
52
35
 
53
- At the same time streams OAV images to redis for later processing (e.g. by murko).
54
- On the first rotation the images from the large ROI are streamed, on the second the
55
- smaller ROI is used.
36
+ def do_thaw():
37
+ yield from bps.abs_set(smargon.omega.velocity, new_velocity, wait=True)
38
+ yield from bps.abs_set(thawer, OnOff.ON, wait=True)
39
+ yield from bps.rel_set(smargon.omega, rotation, wait=True)
40
+ yield from bps.rel_set(smargon.omega, -rotation, wait=True)
56
41
 
57
- Args:
58
- time_to_thaw (float): Time to thaw for, in seconds.
59
- rotation (float, optional): How much to rotate by whilst thawing, in degrees.
60
- Defaults to 360.
61
- ... devices: These are the specific ophyd-devices used for the plan, the
62
- defaults are always correct
63
- """
42
+ def cleanup():
43
+ yield from bps.abs_set(smargon.omega.velocity, initial_velocity, wait=True)
44
+ yield from bps.abs_set(thawer, OnOff.OFF, wait=True)
64
45
 
65
- def switch_forwarder_to_roi() -> MsgGenerator:
66
- yield from bps.complete(oav_to_redis_forwarder, wait=True)
67
- yield from bps.mv(oav_to_redis_forwarder.selected_source, Source.ROI.value)
68
- yield from bps.kickoff(oav_to_redis_forwarder, wait=True)
69
-
70
- yield from _thaw_and_stream_to_redis(
71
- time_to_thaw,
72
- rotation,
73
- robot,
74
- thawer,
75
- smargon,
76
- oav,
77
- oav_to_redis_forwarder,
78
- switch_forwarder_to_roi,
46
+ yield from bpp.contingency_wrapper(
47
+ do_thaw(),
48
+ final_plan=cleanup,
79
49
  )
80
50
 
81
51
 
@@ -85,14 +55,13 @@ def thaw_and_murko_centre(
85
55
  robot: BartRobot = inject("robot"),
86
56
  thawer: Thawer = inject("thawer"),
87
57
  smargon: Smargon = inject("smargon"),
88
- oav: OAV = inject("oav_full_screen"),
89
58
  murko_results: MurkoResultsDevice = inject("murko_results"),
90
59
  oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"),
91
60
  ) -> MsgGenerator:
92
61
  """Thaws the sample and centres it using murko by:
93
62
  1. Turns on the thawer
94
63
  2. Rotates the sample by {rotation} degrees, whilst this is happening images from
95
- the large ROI of the OAV are being fed to murko
64
+ the full screen OAV are being fed to murko
96
65
  3. After the rotation has completed moves to the average centre returned by murko
97
66
  from these images
98
67
  4. Rotate {rotation} degrees back to the start, whilst this is happening images
@@ -109,147 +78,173 @@ def thaw_and_murko_centre(
109
78
  ... devices: These are the specific ophyd-devices used for the plan, the
110
79
  defaults are always correct
111
80
  """
112
-
113
81
  murko_results_group = "get_results"
114
82
 
115
- def centre_then_switch_forwarder_to_roi() -> MsgGenerator:
116
- yield from bps.complete(oav_to_redis_forwarder, wait=True)
83
+ sample_id = yield from bps.rd(robot.sample_id)
84
+ sample_id = int(sample_id)
117
85
 
118
- yield from bps.mv(oav_to_redis_forwarder.selected_source, Source.ROI.value)
86
+ oav_fs = oav_to_redis_forwarder.sources[Source.FULL_SCREEN].oav_ref()
119
87
 
88
+ initial_zoom_level = yield from bps.rd(oav_fs.zoom_controller.level)
89
+ initial_velocity = yield from bps.rd(smargon.omega.velocity)
90
+ new_velocity = abs(rotation / time_to_thaw) * 2.0
91
+ murko_callback = MurkoCallback(
92
+ RedisConstants.REDIS_HOST,
93
+ RedisConstants.REDIS_PASSWORD,
94
+ RedisConstants.MURKO_REDIS_DB,
95
+ )
96
+
97
+ def cleanup():
98
+ yield from bps.mv(oav_fs.zoom_controller.level, initial_zoom_level)
99
+ yield from bps.abs_set(smargon.omega.velocity, initial_velocity, wait=True)
100
+ yield from bps.abs_set(thawer, OnOff.OFF, wait=True)
101
+
102
+ def centre_from_murko():
120
103
  yield from bps.wait(murko_results_group)
104
+
121
105
  x_predict = yield from bps.rd(murko_results.x_mm)
122
106
  y_predict = yield from bps.rd(murko_results.y_mm)
123
107
  z_predict = yield from bps.rd(murko_results.z_mm)
124
108
 
109
+ LOGGER.info(f"Got results: {x_predict, y_predict, z_predict}")
110
+
125
111
  yield from bps.rel_set(smargon.x, x_predict)
126
112
  yield from bps.rel_set(smargon.y, y_predict)
127
113
  yield from bps.rel_set(smargon.z, z_predict)
128
114
 
129
- yield from bps.kickoff(oav_to_redis_forwarder, wait=True)
115
+ @subs_decorator(murko_callback)
116
+ @contingency_decorator(final_plan=cleanup)
117
+ def do_thaw_and_murko_centre():
118
+ yield from bps.mv(
119
+ murko_results.sample_id,
120
+ str(sample_id),
121
+ oav_to_redis_forwarder.sample_id,
122
+ sample_id,
123
+ oav_fs.zoom_controller.level,
124
+ "1.0x",
125
+ )
126
+ yield from bps.abs_set(smargon.omega.velocity, new_velocity, wait=True)
127
+ yield from bps.abs_set(thawer, OnOff.ON, wait=True)
130
128
 
131
- sample_id = yield from bps.rd(robot.sample_id)
132
- yield from bps.mv(murko_results.sample_id, str(sample_id))
129
+ def rotate_in_one_direction_then_murko_centre(
130
+ rotation: float, oav_mode: Source
131
+ ):
132
+ @run_decorator(md={"sample_id": sample_id})
133
+ def rotate_in_one_direction_and_start_murko_and_stream_to_redis():
134
+ yield from bps.stage(murko_results, wait=True)
135
+ yield from bps.trigger(murko_results, group=murko_results_group)
133
136
 
134
- yield from bps.stage(murko_results, wait=True)
135
- yield from bps.trigger(murko_results, group=murko_results_group)
137
+ yield from _rotate_in_one_direction_and_stream_to_redis(
138
+ smargon, oav_to_redis_forwarder, oav_mode, rotation
139
+ )
136
140
 
137
- yield from bpp.contingency_wrapper(
138
- _thaw_and_stream_to_redis(
139
- time_to_thaw,
140
- rotation,
141
- robot,
142
- thawer,
143
- smargon,
144
- oav,
145
- oav_to_redis_forwarder,
146
- centre_then_switch_forwarder_to_roi,
147
- ),
148
- final_plan=partial(bps.unstage, murko_results, wait=True),
149
- )
141
+ yield from rotate_in_one_direction_and_start_murko_and_stream_to_redis()
142
+
143
+ yield from centre_from_murko()
144
+ yield from bps.unstage(murko_results, wait=True)
150
145
 
146
+ yield from rotate_in_one_direction_then_murko_centre(
147
+ rotation, Source.FULL_SCREEN
148
+ )
149
+ yield from rotate_in_one_direction_then_murko_centre(-rotation, Source.ROI)
150
+
151
+ yield from do_thaw_and_murko_centre()
151
152
 
152
- def _thaw(
153
+
154
+ def thaw_and_stream_to_redis(
153
155
  time_to_thaw: float,
154
- rotation: float,
155
- thawer: Thawer,
156
- smargon: Smargon,
157
- plan_between_rotations: Callable[[], MsgGenerator] | None = None,
156
+ rotation: float = 360,
157
+ robot: BartRobot = inject("robot"),
158
+ thawer: Thawer = inject("thawer"),
159
+ smargon: Smargon = inject("smargon"),
160
+ oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"),
158
161
  ) -> MsgGenerator:
159
162
  """Turns on the thawer and rotates the sample by {rotation} degrees to thaw it, then
160
163
  rotates {rotation} degrees back and turns the thawer off. The speed of the goniometer
161
164
  is set such that the process takes whole process will take {time_to_thaw} time.
162
165
 
166
+ At the same time streams OAV images to redis for later processing (e.g. by murko).
167
+ On the first rotation the images from the large ROI are streamed, on the second the
168
+ smaller ROI is used.
169
+
163
170
  Args:
164
171
  time_to_thaw (float): Time to thaw for, in seconds.
165
- rotation (float): How much to rotate by whilst thawing, in degrees.
166
- thawer (Thawer): The thawing device.
167
- smargon (Smargon): The smargon used to rotate.
168
- plan_between_rotations (MsgGenerator, optional): A plan to run between rotations
169
- of the smargon. Defaults to no plan.
172
+ rotation (float, optional): How much to rotate by whilst thawing, in degrees.
173
+ Defaults to 360.
174
+ ... devices: These are the specific ophyd-devices used for the plan, the
175
+ defaults are always correct
170
176
  """
177
+ sample_id = yield from bps.rd(robot.sample_id)
178
+ sample_id = int(sample_id)
179
+
180
+ oav_fs = oav_to_redis_forwarder.sources[Source.FULL_SCREEN].oav_ref()
181
+
182
+ initial_zoom_level = yield from bps.rd(oav_fs.zoom_controller.level)
171
183
  initial_velocity = yield from bps.rd(smargon.omega.velocity)
172
184
  new_velocity = abs(rotation / time_to_thaw) * 2.0
173
185
 
174
- def do_thaw():
175
- yield from bps.abs_set(smargon.omega.velocity, new_velocity, wait=True)
176
- yield from bps.abs_set(thawer.control, OnOff.ON, wait=True)
177
- yield from bps.rel_set(smargon.omega, rotation, wait=True)
178
- if plan_between_rotations:
179
- yield from plan_between_rotations()
180
- yield from bps.rel_set(smargon.omega, -rotation, wait=True)
186
+ murko_callback = MurkoCallback(
187
+ RedisConstants.REDIS_HOST,
188
+ RedisConstants.REDIS_PASSWORD,
189
+ RedisConstants.MURKO_REDIS_DB,
190
+ )
181
191
 
182
192
  def cleanup():
193
+ yield from bps.mv(oav_fs.zoom_controller.level, initial_zoom_level)
183
194
  yield from bps.abs_set(smargon.omega.velocity, initial_velocity, wait=True)
184
- yield from bps.abs_set(thawer.control, OnOff.OFF, wait=True)
195
+ yield from bps.abs_set(thawer, OnOff.OFF, wait=True)
185
196
 
186
- # Always cleanup even if there is a failure
187
- yield from bpp.contingency_wrapper(
188
- do_thaw(),
189
- final_plan=cleanup,
190
- )
191
-
192
-
193
- def _thaw_and_stream_to_redis(
194
- time_to_thaw: float,
195
- rotation: float,
196
- robot: BartRobot,
197
- thawer: Thawer,
198
- smargon: Smargon,
199
- oav: OAV,
200
- oav_to_redis_forwarder: OAVToRedisForwarder,
201
- plan_between_rotations: Callable[[], MsgGenerator],
202
- ) -> MsgGenerator:
203
- zoom_percentage = yield from bps.rd(oav.zoom_controller.percentage)
204
- sample_id = yield from bps.rd(robot.sample_id)
205
-
206
- sample_id = int(sample_id)
207
- zoom_level_before_thawing = yield from bps.rd(oav.zoom_controller.level)
208
-
209
- yield from bps.mv(oav.zoom_controller.level, "1.0x")
210
-
211
- microns_per_pixel_x = yield from bps.rd(oav.microns_per_pixel_x)
212
- microns_per_pixel_y = yield from bps.rd(oav.microns_per_pixel_y)
213
- beam_centre_i = yield from bps.rd(oav.beam_centre_i)
214
- beam_centre_j = yield from bps.rd(oav.beam_centre_j)
215
-
216
- @subs_decorator(
217
- MurkoCallback(
218
- RedisConstants.REDIS_HOST,
219
- RedisConstants.REDIS_PASSWORD,
220
- RedisConstants.MURKO_REDIS_DB,
221
- )
222
- )
223
- @run_decorator(
224
- md={
225
- "microns_per_x_pixel": microns_per_pixel_x,
226
- "microns_per_y_pixel": microns_per_pixel_y,
227
- "beam_centre_i": beam_centre_i,
228
- "beam_centre_j": beam_centre_j,
229
- "zoom_percentage": zoom_percentage,
230
- "sample_id": sample_id,
231
- }
232
- )
233
- def _main_plan():
197
+ @subs_decorator(murko_callback)
198
+ @contingency_decorator(final_plan=cleanup)
199
+ def do_thaw_and_stream_to_redis():
234
200
  yield from bps.mv(
235
201
  oav_to_redis_forwarder.sample_id,
236
202
  sample_id,
237
- oav_to_redis_forwarder.selected_source,
238
- Source.FULL_SCREEN.value,
203
+ oav_fs.zoom_controller.level,
204
+ "1.0x",
239
205
  )
240
-
241
- yield from bps.kickoff(oav_to_redis_forwarder, wait=True)
242
- yield from bps.monitor(smargon.omega.user_readback, name="smargon")
243
- yield from bps.monitor(oav_to_redis_forwarder.uuid, name="oav")
244
- yield from _thaw(
245
- time_to_thaw, rotation, thawer, smargon, plan_between_rotations
206
+ yield from bps.abs_set(smargon.omega.velocity, new_velocity, wait=True)
207
+ yield from bps.abs_set(thawer, OnOff.ON, wait=True)
208
+
209
+ @run_decorator(md={"sample_id": sample_id})
210
+ def rotate_in_one_direction_and_stream_to_redis(
211
+ rotation: float, oav_mode: Source
212
+ ):
213
+ yield from _rotate_in_one_direction_and_stream_to_redis(
214
+ smargon, oav_to_redis_forwarder, oav_mode, rotation
215
+ )
216
+
217
+ yield from rotate_in_one_direction_and_stream_to_redis(
218
+ rotation, Source.FULL_SCREEN
246
219
  )
247
- yield from bps.complete(oav_to_redis_forwarder)
220
+ yield from rotate_in_one_direction_and_stream_to_redis(-rotation, Source.ROI)
248
221
 
249
- def cleanup():
250
- yield from bps.mv(oav.zoom_controller.level, zoom_level_before_thawing)
222
+ yield from do_thaw_and_stream_to_redis()
251
223
 
252
- yield from bpp.contingency_wrapper(
253
- _main_plan(),
254
- final_plan=cleanup,
224
+
225
+ def _rotate_in_one_direction_and_stream_to_redis(
226
+ smargon: Smargon,
227
+ oav_to_redis_forwarder: OAVToRedisForwarder,
228
+ oav_mode: Source,
229
+ rotation: float,
230
+ ):
231
+ def get_metadata_from_current_oav():
232
+ current_source_idx = yield from bps.rd(oav_to_redis_forwarder.selected_source)
233
+ oav = oav_to_redis_forwarder.sources[current_source_idx].oav_ref()
234
+ yield from bps.create()
235
+ oav_info = yield from bps.read(oav)
236
+ LOGGER.info(f"Got oav information: {oav_info}")
237
+ yield from bps.save()
238
+
239
+ yield from bps.mv(
240
+ oav_to_redis_forwarder.selected_source,
241
+ oav_mode.value,
255
242
  )
243
+
244
+ yield from get_metadata_from_current_oav()
245
+ yield from bps.monitor(smargon.omega.user_readback, name="smargon")
246
+ yield from bps.monitor(oav_to_redis_forwarder.uuid, name="oav")
247
+
248
+ yield from bps.kickoff(oav_to_redis_forwarder, wait=True)
249
+ yield from bps.rel_set(smargon.omega, rotation, wait=True)
250
+ yield from bps.complete(oav_to_redis_forwarder, wait=True)
@@ -112,6 +112,8 @@ class DCID:
112
112
  match expt_params.detector_name:
113
113
  case "eiger":
114
114
  self.detector = Eiger()
115
+ case _:
116
+ raise ValueError("Unknown detector:", expt_params.detector_name)
115
117
 
116
118
  self.server = server or DEFAULT_ISPYB_SERVER
117
119
  self.emit_errors = emit_errors
@@ -144,7 +146,7 @@ class DCID:
144
146
  try:
145
147
  if not start_time:
146
148
  start_time = datetime.datetime.now().astimezone()
147
- elif not start_time.timetz:
149
+ else:
148
150
  start_time = start_time.astimezone()
149
151
 
150
152
  resolution = get_resolution(
@@ -156,10 +158,7 @@ class DCID:
156
158
  transmission = self.parameters.transmission * 100
157
159
  xbeam, ybeam = beam_settings.beam_center_in_mm
158
160
 
159
- if isinstance(self.detector, Eiger):
160
- start_image_number = 1
161
- else:
162
- raise ValueError("Unknown detector:", self.detector)
161
+ start_image_number = 1
163
162
 
164
163
  events = [
165
164
  {
@@ -253,6 +253,7 @@ def main_extruder_plan(
253
253
  parameters.exposure_time_s,
254
254
  ],
255
255
  dcm,
256
+ detector_stage,
256
257
  )
257
258
  yield from setup_zebra_for_extruder_with_pump_probe_plan(
258
259
  zebra,
@@ -275,6 +276,7 @@ def main_extruder_plan(
275
276
  parameters.exposure_time_s,
276
277
  ],
277
278
  dcm,
279
+ detector_stage,
278
280
  )
279
281
  yield from setup_zebra_for_quickshot_plan(
280
282
  zebra, parameters.exposure_time_s, parameters.num_images, wait=True
@@ -374,6 +376,7 @@ def tidy_up_at_collection_end_plan(
374
376
  parameters: ExtruderParameters,
375
377
  dcid: DCID,
376
378
  dcm: DCM,
379
+ detector_stage: YZStage,
377
380
  ) -> MsgGenerator:
378
381
  """A plan to tidy up at the end of a collection, successful or aborted.
379
382
 
@@ -386,7 +389,7 @@ def tidy_up_at_collection_end_plan(
386
389
 
387
390
  # Clean Up
388
391
  if parameters.detector_name == "eiger":
389
- yield from sup.eiger("return-to-normal", None, dcm)
392
+ yield from sup.eiger("return-to-normal", None, dcm, detector_stage)
390
393
  SSX_LOGGER.debug(f"{parameters.filename}_{caget(pv.eiger_seq_id)}")
391
394
  SSX_LOGGER.debug("End of Run")
392
395
  SSX_LOGGER.info("Close hutch shutter")
@@ -452,7 +455,7 @@ def run_plan_in_wrapper(
452
455
  ),
453
456
  final_plan=lambda: (
454
457
  yield from tidy_up_at_collection_end_plan(
455
- zebra, shutter, parameters, dcid, dcm
458
+ zebra, shutter, parameters, dcid, dcm, detector_stage
456
459
  )
457
460
  ),
458
461
  auto_raise=False,