mx-bluesky 1.4.1a0__py3-none-any.whl → 1.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
  3. mx_bluesky/beamlines/i24/serial/__init__.py +0 -6
  4. mx_bluesky/beamlines/i24/serial/dcid.py +125 -151
  5. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
  6. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +88 -43
  7. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +2 -46
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +85 -122
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +58 -66
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +1 -19
  12. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +11 -2
  13. mx_bluesky/beamlines/i24/serial/parameters/constants.py +16 -2
  14. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +94 -19
  15. mx_bluesky/beamlines/i24/serial/parameters/utils.py +19 -0
  16. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
  17. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +61 -8
  18. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +81 -40
  19. mx_bluesky/beamlines/i24/serial/write_nexus.py +66 -67
  20. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/aperture_change_callback.py +1 -1
  21. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/grid_detection_callback.py +19 -1
  22. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/ispyb_callback_base.py +40 -34
  23. mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/ispyb_mapping.py +4 -4
  24. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/logging_callback.py +1 -1
  25. mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/zocalo_callback.py +14 -9
  26. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_callback.py +46 -38
  27. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_mapping.py +2 -2
  28. mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/nexus_callback.py +20 -15
  29. mx_bluesky/common/external_interaction/config_server.py +11 -0
  30. mx_bluesky/common/external_interaction/ispyb/__init__.py +0 -0
  31. mx_bluesky/{hyperion → common}/external_interaction/ispyb/data_model.py +2 -0
  32. mx_bluesky/{hyperion → common}/external_interaction/ispyb/exp_eye_store.py +67 -17
  33. mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_store.py +20 -18
  34. mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_utils.py +2 -2
  35. mx_bluesky/common/external_interaction/nexus/__init__.py +0 -0
  36. mx_bluesky/{hyperion → common}/external_interaction/nexus/nexus_utils.py +21 -6
  37. mx_bluesky/{hyperion → common}/external_interaction/nexus/write_nexus.py +5 -5
  38. mx_bluesky/common/external_interaction/test_config_server.py +38 -0
  39. mx_bluesky/common/parameters/components.py +10 -8
  40. mx_bluesky/common/parameters/constants.py +6 -0
  41. mx_bluesky/common/parameters/gridscan.py +102 -53
  42. mx_bluesky/common/plans/do_fgs.py +4 -4
  43. mx_bluesky/{hyperion → common/utils}/exceptions.py +27 -1
  44. mx_bluesky/common/utils/log.py +17 -7
  45. mx_bluesky/hyperion/__main__.py +15 -14
  46. mx_bluesky/hyperion/device_setup_plans/check_beamstop.py +27 -0
  47. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +34 -37
  48. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +7 -7
  49. mx_bluesky/hyperion/device_setup_plans/position_detector.py +1 -1
  50. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +3 -3
  51. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +21 -4
  52. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +62 -36
  53. mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
  54. mx_bluesky/hyperion/device_setup_plans/utils.py +4 -0
  55. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +8 -8
  56. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +28 -17
  57. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +10 -1
  58. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  59. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +54 -58
  60. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +22 -31
  61. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +57 -40
  62. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +3 -3
  63. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -2
  64. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +6 -14
  65. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +12 -11
  66. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +4 -4
  67. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +39 -30
  68. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +36 -18
  69. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +33 -21
  70. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -9
  71. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
  72. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +31 -20
  73. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -30
  74. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +39 -24
  75. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +25 -24
  76. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -1
  77. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +13 -9
  78. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +50 -0
  80. mx_bluesky/hyperion/external_interaction/config_server.py +15 -1
  81. mx_bluesky/hyperion/parameters/components.py +3 -2
  82. mx_bluesky/hyperion/parameters/constants.py +1 -0
  83. mx_bluesky/hyperion/parameters/gridscan.py +56 -89
  84. mx_bluesky/hyperion/parameters/load_centre_collect.py +51 -6
  85. mx_bluesky/hyperion/parameters/robot_load.py +40 -0
  86. mx_bluesky/hyperion/parameters/rotation.py +28 -3
  87. mx_bluesky/hyperion/utils/context.py +1 -1
  88. mx_bluesky/hyperion/utils/validation.py +5 -3
  89. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/METADATA +6 -6
  90. mx_bluesky-1.4.3.dist-info/RECORD +155 -0
  91. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/WHEEL +1 -1
  92. mx_bluesky/common/parameters/robot_load.py +0 -16
  93. mx_bluesky/hyperion/external_interaction/exceptions.py +0 -13
  94. mx_bluesky/hyperion/log.py +0 -15
  95. mx_bluesky-1.4.1a0.dist-info/RECORD +0 -150
  96. /mx_bluesky/{hyperion/external_interaction/callbacks/xray_centre → common/external_interaction}/__init__.py +0 -0
  97. /mx_bluesky/{hyperion/external_interaction/ispyb → common/external_interaction/callbacks/common}/__init__.py +0 -0
  98. /mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/abstract_event.py +0 -0
  99. /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/log_uid_tag_callback.py +0 -0
  100. /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/plan_reactive_callback.py +0 -0
  101. /mx_bluesky/{hyperion/external_interaction/nexus → common/external_interaction/callbacks/xray_centre}/__init__.py +0 -0
  102. /mx_bluesky/{hyperion → common}/utils/utils.py +0 -0
  103. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/LICENSE +0 -0
  104. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/entry_points.txt +0 -0
  105. {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/top_level.txt +0 -0
mx_bluesky/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.4.1a0'
16
- __version_tuple__ = version_tuple = (1, 4, 1)
15
+ __version__ = version = '1.4.3'
16
+ __version_tuple__ = version_tuple = (1, 4, 3)
@@ -0,0 +1,178 @@
1
+ import io
2
+ import json
3
+ import pickle
4
+ from typing import TypedDict
5
+
6
+ import numpy as np
7
+ import zmq
8
+ from dodal.beamlines.i04 import MURKO_REDIS_DB, REDIS_HOST, REDIS_PASSWORD
9
+ from numpy.typing import NDArray
10
+ from PIL import Image
11
+ from redis import StrictRedis
12
+
13
+ from mx_bluesky.common.utils.log import LOGGER
14
+
15
+ MURKO_ADDRESS = "tcp://i04-murko-prod.diamond.ac.uk:8008"
16
+
17
+
18
+ class MurkoRequest(TypedDict):
19
+ to_predict: NDArray
20
+ model_img_size: tuple[int, int]
21
+ save: bool
22
+ min_size: int
23
+ description: list
24
+ prefix: list[str]
25
+
26
+
27
+ def get_image_size(image: NDArray) -> tuple[int, int]:
28
+ """Returns the width and height of a numpy image"""
29
+ return image.shape[1], image.shape[0]
30
+
31
+
32
+ def send_to_murko_and_get_results(request: MurkoRequest) -> dict:
33
+ LOGGER.info(f"Sending {request['prefix']} to murko")
34
+ context = zmq.Context()
35
+ socket = context.socket(zmq.REQ)
36
+ socket.connect(MURKO_ADDRESS)
37
+ socket.send(pickle.dumps(request))
38
+ raw_results = socket.recv()
39
+ results = pickle.loads(raw_results)
40
+ LOGGER.info(f"Got {len(results['descriptions'])} results")
41
+ return results
42
+
43
+
44
+ def correlate_results_to_uuids(request: MurkoRequest, murko_results: dict) -> list:
45
+ results = []
46
+ uuids = request["prefix"]
47
+
48
+ width, height = get_image_size(request["to_predict"][0])
49
+
50
+ for uuid, prediction in zip(uuids, murko_results["descriptions"], strict=False):
51
+ coords = prediction["most_likely_click"]
52
+ y_coord = coords[0] * height
53
+ x_coord = coords[1] * width
54
+ results.append(
55
+ {"uuid": uuid, "x_pixel_coord": x_coord, "y_pixel_coord": y_coord}
56
+ )
57
+ return results
58
+
59
+
60
+ class BatchMurkoForwarder:
61
+ def __init__(self, redis_client: StrictRedis, batch_size: int):
62
+ """
63
+ Holds image data streamed from redis and forwards it to murko when:
64
+ * A set number have been received
65
+ * The shape of the images changes
66
+ * When `flush` is called
67
+
68
+ Once data has been forwarded this will then wait on the results and put them
69
+ back in redis.
70
+
71
+ Args:
72
+ redis_client: The client to send murko results back to redis.
73
+ batch_size: How many results to accumulate until they are flushed to redis.
74
+ """
75
+ self.redis_client = redis_client
76
+ self.batch_size = batch_size
77
+ self._uuids_and_images: dict[str, NDArray] = {}
78
+ self._last_image_size: tuple[int, int] | None = None
79
+ self._last_sample_id = ""
80
+
81
+ def _handle_batch_of_images(self, sample_id, images, uuids):
82
+ request_arguments: MurkoRequest = {
83
+ "model_img_size": (256, 320),
84
+ "to_predict": np.array(images),
85
+ "save": False,
86
+ "min_size": 64,
87
+ "description": [
88
+ "foreground",
89
+ "crystal",
90
+ "loop_inside",
91
+ "loop",
92
+ ["crystal", "loop"],
93
+ ["crystal", "loop", "stem"],
94
+ ],
95
+ "prefix": uuids,
96
+ }
97
+ predictions = send_to_murko_and_get_results(request_arguments)
98
+ results = correlate_results_to_uuids(request_arguments, predictions)
99
+ self._send_murko_results_to_redis(sample_id, results)
100
+
101
+ def _send_murko_results_to_redis(self, sample_id: str, results: list):
102
+ for result in results:
103
+ self.redis_client.hset(
104
+ f"murko:{sample_id}:results", result["uuid"], json.dumps(result)
105
+ )
106
+ self.redis_client.publish("murko-results", json.dumps(results))
107
+
108
+ def add(self, sample_id: str, uuid: str, image: NDArray):
109
+ """Add an image to the batch to send to murko."""
110
+ image_size = get_image_size(image)
111
+ self._last_sample_id = sample_id
112
+ if self._last_image_size and self._last_image_size != image_size:
113
+ self.flush()
114
+ self._uuids_and_images[uuid] = image
115
+ self._last_image_size = image_size
116
+ if len(self._uuids_and_images.keys()) >= self.batch_size:
117
+ self.flush()
118
+
119
+ def flush(self):
120
+ """Flush the batch to murko."""
121
+ if self._uuids_and_images:
122
+ self._handle_batch_of_images(
123
+ self._last_sample_id,
124
+ list(self._uuids_and_images.values()),
125
+ list(self._uuids_and_images.keys()),
126
+ )
127
+ self._uuids_and_images = {}
128
+ self._last_image_size = None
129
+
130
+
131
+ class RedisListener:
132
+ TIMEOUT_S = 2
133
+
134
+ def __init__(
135
+ self,
136
+ redis_host=REDIS_HOST,
137
+ redis_password=REDIS_PASSWORD,
138
+ db=MURKO_REDIS_DB,
139
+ redis_channel="murko",
140
+ ):
141
+ self.redis_client = StrictRedis(
142
+ host=redis_host,
143
+ password=redis_password,
144
+ db=db,
145
+ )
146
+ self.pubsub = self.redis_client.pubsub()
147
+ self.channel = redis_channel
148
+ self.forwarder = BatchMurkoForwarder(self.redis_client, 10)
149
+
150
+ def _get_and_handle_message(self):
151
+ message = self.pubsub.get_message(timeout=self.TIMEOUT_S)
152
+ if message and message["type"] == "message":
153
+ data = json.loads(message["data"])
154
+ LOGGER.info(f"Received from redis: {data}")
155
+ uuid = data["uuid"]
156
+ sample_id = data["sample_id"]
157
+
158
+ # Images are put in redis as raw jpeg bytes, murko needs numpy arrays
159
+ raw_image = self.redis_client.hget(f"murko:{sample_id}:raw", uuid)
160
+ assert isinstance(raw_image, bytes)
161
+ image = Image.open(io.BytesIO(raw_image))
162
+ image = np.asarray(image)
163
+
164
+ self.forwarder.add(sample_id, uuid, image)
165
+
166
+ elif not message:
167
+ self.forwarder.flush()
168
+
169
+ def listen_for_image_data_forever(self):
170
+ self.pubsub.subscribe(self.channel)
171
+
172
+ while True:
173
+ self._get_and_handle_message()
174
+
175
+
176
+ if __name__ == "__main__":
177
+ client = RedisListener()
178
+ client.listen_for_image_data_forever()
@@ -18,9 +18,6 @@ from .fixed_target.i24ssx_Chip_Manager_py3v1 import (
18
18
  moveto,
19
19
  moveto_preset,
20
20
  pumpprobe_calc,
21
- save_screen_map,
22
- upload_parameters,
23
- write_parameter_file,
24
21
  )
25
22
  from .log import clean_up_log_config_at_end, setup_collection_logs
26
23
  from .setup_beamline.setup_detector import setup_detector_stage
@@ -44,9 +41,6 @@ __all__ = [
44
41
  "load_lite_map",
45
42
  "load_stock_map",
46
43
  "pumpprobe_calc",
47
- "save_screen_map",
48
- "upload_parameters",
49
- "write_parameter_file",
50
44
  "setup_collection_logs",
51
45
  "clean_up_log_config_at_end",
52
46
  ]
@@ -2,29 +2,26 @@ import datetime
2
2
  import json
3
3
  import math
4
4
  import os
5
- import re
6
5
  import subprocess
7
- import warnings
8
6
  from functools import lru_cache
9
7
 
8
+ import bluesky.plan_stubs as bps
10
9
  import requests
10
+ from dodal.beamlines import i24
11
+ from dodal.devices.i24.beam_center import DetectorBeamCenter
12
+ from dodal.devices.i24.dcm import DCM
13
+ from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
14
+ from dodal.devices.i24.pilatus_metadata import PilatusMetadata
11
15
 
16
+ from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import PumpProbeSetting
12
17
  from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
13
- from mx_bluesky.beamlines.i24.serial.parameters import SSXType
14
- from mx_bluesky.beamlines.i24.serial.setup_beamline import (
15
- Detector,
16
- Eiger,
17
- Pilatus,
18
- caget,
19
- cagetstring,
20
- pv,
18
+ from mx_bluesky.beamlines.i24.serial.parameters import (
19
+ BeamSettings,
20
+ DetectorName,
21
+ ExtruderParameters,
22
+ FixedTargetParameters,
21
23
  )
22
-
23
- try:
24
- from typing import Literal
25
- except ImportError:
26
- pass
27
-
24
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import Detector, Eiger, Pilatus
28
25
 
29
26
  # Collection start/end script to kick off analysis
30
27
  COLLECTION_START_SCRIPT = "/dls_sw/i24/scripts/RunAtStartOfCollect-i24-ssx.sh"
@@ -49,19 +46,54 @@ def get_auth_header() -> dict:
49
46
  return {"Authorization": "Bearer " + token}
50
47
 
51
48
 
52
- class DCID:
49
+ def read_beam_info_from_hardware(
50
+ dcm: DCM,
51
+ mirrors: FocusMirrorsMode,
52
+ beam_center: DetectorBeamCenter,
53
+ detector_name: DetectorName,
54
+ ):
55
+ """ Read the beam information from hardware.
56
+
57
+ Args:
58
+ dcm (DCM): The decm device.
59
+ mirrors (FocusMirrorMode): The device describing the focus mirror mode settings.
60
+ beam_center (DetectorBeamCenter): A device to set and read the beam center on \
61
+ the detector.
62
+ detector_name (DetectorName): The detector currently in use.
63
+
64
+ Returns:
65
+ BeamSettings parameter model.
53
66
  """
54
- Interfaces with ISPyB to allow ssx DCID/synchweb interaction.
67
+ wavelength = yield from bps.rd(dcm.wavelength_in_a)
68
+ beamsize_x = yield from bps.rd(mirrors.beam_size_x)
69
+ beamsize_y = yield from bps.rd(mirrors.beam_size_y)
70
+ pixel_size = (
71
+ Eiger().pixel_size_mm if detector_name == "eiger" else Pilatus().pixel_size_mm
72
+ )
73
+ beam_center_x = yield from bps.rd(beam_center.beam_x)
74
+ beam_center_y = yield from bps.rd(beam_center.beam_y)
75
+ return BeamSettings(
76
+ wavelength_in_a=wavelength,
77
+ beam_size_in_um=(beamsize_x, beamsize_y),
78
+ beam_center_in_mm=(
79
+ beam_center_x * pixel_size[0],
80
+ beam_center_y * pixel_size[1],
81
+ ),
82
+ )
83
+
84
+
85
+ class DCID:
86
+ """ Interfaces with ISPyB to allow ssx DCID/synchweb interaction.
55
87
 
56
88
  Args:
57
- server: The URL for the bridge server, if not the default.
58
- emit_errors:
59
- If False, errors while interacting with the DCID server will
60
- not be propagated to the caller. This decides if you want to
61
- stop collection if you can't get a DCID
62
- timeout: Length of time to wait for the DB server before giving up
63
- ssx_type: The type of SSX experiment this is for
64
- detector: The detector in use for current collection.
89
+ server (str, optional): The URL for the bridge server, if not the default.
90
+ emit_errors (bool, optional): If False, errors while interacting with the DCID \
91
+ server will not be propagated to the caller. This decides if you want to \
92
+ stop collection if you can't get a DCID. Defaults to True.
93
+ timeout (float, optional): Length of time in s to wait for the DB server before \
94
+ giving up. Defaults to 10 s.
95
+ expt_parameters (ExtruderParameters | FixedTargetParameters): Collection \
96
+ parameters input by user.
65
97
 
66
98
 
67
99
  Attributes:
@@ -75,47 +107,44 @@ class DCID:
75
107
  server: str | None = None,
76
108
  emit_errors: bool = True,
77
109
  timeout: float = 10,
78
- ssx_type: SSXType = SSXType.FIXED,
79
- detector: Detector | Literal["eiger", "pilatus"] | None = None,
110
+ expt_params: ExtruderParameters | FixedTargetParameters,
80
111
  ):
112
+ self.parameters = expt_params
81
113
  self.detector: Detector
82
114
  # Handle case of string literal
83
- if detector == "eiger":
84
- self.detector = Eiger()
85
- elif detector == "pilatus":
86
- self.detector = Pilatus()
87
- elif detector is None:
88
- self.detector = Pilatus()
89
- warnings.warn(
90
- "Please pass detector= to DCID. Pilatus assumed, this will be removed in the future.",
91
- UserWarning,
92
- stacklevel=5,
93
- )
115
+ match expt_params.detector_name:
116
+ case "eiger":
117
+ self.detector = Eiger()
118
+ case "pilatus":
119
+ self.detector = Pilatus()
94
120
 
95
121
  self.server = server or DEFAULT_ISPYB_SERVER
96
122
  self.emit_errors = emit_errors
97
123
  self.error = False
98
124
  self.timeout = timeout
99
- self.ssx_type = SSXType(ssx_type)
100
125
  self.dcid = None
101
126
 
102
127
  def generate_dcid(
103
128
  self,
104
- visit: str,
129
+ beam_settings: BeamSettings,
105
130
  image_dir: str,
131
+ file_template: str,
106
132
  num_images: int,
107
- exposure_time: float,
108
- start_time: datetime.datetime | None = None,
109
133
  shots_per_position: int = 1,
110
- pump_exposure_time: float | None = None,
111
- pump_delay: float = 0,
112
- pump_status: int = 0,
134
+ start_time: datetime.datetime | None = None,
135
+ pump_probe: bool = False,
113
136
  ):
114
137
  """Generate an ispyb DCID.
115
138
 
116
139
  Args:
117
- visit: The name of the visit e.g. "mx12345-4"
118
- image_dir: The location the images will be written
140
+ beam_settings (BeamSettings): Information about the beam read from hardware.
141
+ image_dir (str): The location the images will be written to.
142
+ num_images (int): Total number of images to be collected.
143
+ shots_per_position (int, optional): Number of exposures per position in a \
144
+ chip. Defaults to 1, which works for extruder.
145
+ start_time(datetime, optional): Collection start time. Defaults to None.
146
+ pump_probe (bool, optional): If True, a pump probe collection is running. \
147
+ Defaults to False.
119
148
  """
120
149
  try:
121
150
  if not start_time:
@@ -123,22 +152,18 @@ class DCID:
123
152
  elif not start_time.timetz:
124
153
  start_time = start_time.astimezone()
125
154
 
126
- # Gather data from the beamline
127
- detector_distance = float(caget(self.detector.pv.detector_distance))
128
- wavelength = float(caget(self.detector.pv.wavelength))
129
- resolution = get_resolution(self.detector, detector_distance, wavelength)
130
- beamsize_x, beamsize_y = get_beamsize()
131
- transmission = float(caget(self.detector.pv.transmission)) * 100
132
- xbeam, ybeam = get_beam_center(self.detector)
155
+ resolution = get_resolution(
156
+ self.detector,
157
+ self.parameters.detector_distance_mm,
158
+ beam_settings.wavelength_in_a,
159
+ )
160
+ beamsize_x, beamsize_y = beam_settings.beam_size_in_um
161
+ transmission = self.parameters.transmission * 100
162
+ xbeam, ybeam = beam_settings.beam_center_in_mm
133
163
 
134
164
  if isinstance(self.detector, Pilatus):
135
- # Mirror the construction that the PPU does
136
- fileTemplate = get_pilatus_filename_template_from_pvs()
137
165
  startImageNumber = 0
138
166
  elif isinstance(self.detector, Eiger):
139
- # Eiger base filename is directly written to the PV
140
- # Nexgen then uses this to write the .nxs file
141
- fileTemplate = str(cagetstring(self.detector.pv.file_name)) + ".nxs"
142
167
  startImageNumber = 1
143
168
  else:
144
169
  raise ValueError("Unknown detector:", self.detector)
@@ -147,48 +172,48 @@ class DCID:
147
172
  {
148
173
  "name": "Xray probe",
149
174
  "offset": 0,
150
- "duration": exposure_time,
151
- "period": exposure_time,
175
+ "duration": self.parameters.exposure_time_s,
176
+ "period": self.parameters.exposure_time_s,
152
177
  "repetition": shots_per_position,
153
178
  "eventType": "XrayDetection",
154
179
  }
155
180
  ]
156
- if pump_status > 0:
157
- # https://confluence.diamond.ac.uk/pages/viewpage.action?pageId=131238829
158
- # https://confluence.diamond.ac.uk/display/MXTech/Dynamics+and+fixed+targets
159
- # pump_status = 0: no pump probe
160
- # pump_status = 1: pump then probe
161
- # pump_status = 2: pump within probe
162
- # pump_status = 3-7: different EAVA modes (i.e. also pump then probe)
163
- if pump_status != 2 and self.ssx_type is SSXType.FIXED:
164
- # Pump status could be 1 for extruder but not have this.
165
- # pump then probe - pump_delay corresponds to time *before* first image
166
- pump_delay = -pump_delay
181
+ if pump_probe:
182
+ match self.parameters:
183
+ case FixedTargetParameters():
184
+ # pump then probe - pump_delay corresponds to time *before* first image
185
+ pump_delay = (
186
+ -self.parameters.laser_delay_s
187
+ if self.parameters.pump_repeat
188
+ is not PumpProbeSetting.Short2
189
+ else self.parameters.laser_delay_s
190
+ )
191
+ case ExtruderParameters():
192
+ pump_delay = self.parameters.laser_delay_s
167
193
  events.append(
168
194
  {
169
195
  "name": "Laser probe",
170
196
  "offset": pump_delay,
171
- "duration": pump_exposure_time,
172
- # "period": None,
197
+ "duration": self.parameters.laser_dwell_s,
173
198
  "repetition": 1,
174
199
  "eventType": "LaserExcitation",
175
200
  },
176
201
  )
177
202
 
178
203
  data = {
179
- "detectorDistance": float(detector_distance),
204
+ "detectorDistance": self.parameters.detector_distance_mm,
180
205
  "detectorId": self.detector.id,
181
- "exposureTime": float(exposure_time),
182
- "fileTemplate": fileTemplate,
183
- "imageDirectory": str(image_dir),
184
- "numberOfImages": int(num_images),
185
- "resolution": float(resolution),
206
+ "exposureTime": self.parameters.exposure_time_s,
207
+ "fileTemplate": file_template,
208
+ "imageDirectory": image_dir,
209
+ "numberOfImages": num_images,
210
+ "resolution": resolution,
186
211
  "startImageNumber": startImageNumber,
187
212
  "startTime": start_time.isoformat(),
188
- "transmission": float(transmission),
189
- "visit": visit,
190
- "wavelength": float(wavelength),
191
- "group": {"experimentType": self.ssx_type.value},
213
+ "transmission": transmission,
214
+ "visit": self.parameters.visit.name,
215
+ "wavelength": beam_settings.wavelength_in_a,
216
+ "group": {"experimentType": self.parameters.ispyb_experiment_type},
192
217
  "xBeam": xbeam,
193
218
  "yBeam": ybeam,
194
219
  "ssx": {
@@ -334,84 +359,33 @@ class DCID:
334
359
  SSX_LOGGER.warning("Error completing DCID: %s (%s)", e, resp_str)
335
360
 
336
361
 
337
- def get_pilatus_filename_template_from_pvs() -> str:
338
- """
339
- Get the template file path by querying the detector PVs.
340
-
341
- Returns: A template string, with the image numbers replaced with '#'
342
- """
343
-
344
- filename = cagetstring(pv.pilat_filename)
345
- filename_template = cagetstring(pv.pilat_filetemplate)
346
- file_number = int(caget(pv.pilat_filenumber))
347
- # Exploit fact that passing negative numbers will put the - before the 0's
348
- expected_filename = str(filename_template % (filename, f"{file_number:05d}_", -9))
349
- # Now, find the -09 part of this
350
- numberpart = re.search(r"(-0+9)", expected_filename)
351
- # Make sure this was the only one
352
- if numberpart is not None:
353
- assert re.search(r"(-0+9)", expected_filename[numberpart.end() :]) is None
354
- template_fill = "#" * len(numberpart.group(0))
355
- return (
356
- expected_filename[: numberpart.start()]
357
- + template_fill
358
- + expected_filename[numberpart.end() :]
359
- )
360
- else:
361
- raise ValueError(f"{filename=} did not contain the numbers for templating")
362
-
363
-
364
- def get_beamsize() -> tuple[float | None, float | None]:
362
+ def get_pilatus_filename_template_from_device():
365
363
  """
366
- Read the PVs to get the current beamsize.
364
+ Get the template file path by querying the detector PVs, mirror the construction \
365
+ that the PPU does.
367
366
 
368
367
  Returns:
369
- A tuple (x, y) of beam size (in µm). These values can be 'None'
370
- if the focus mode was unrecognised.
368
+ A template string, with the image numbers replaced with '#'
371
369
  """
372
- # These I24 modes are from GDA
373
- focus_modes = {
374
- "focus10": ("7x7", 7, 7),
375
- "focus20d": ("20x20", 20, 20),
376
- "focus30d": ("30x30", 30, 30),
377
- "focus50d": ("50x50", 50, 50),
378
- "focus1050d": ("10x50", 10, 50),
379
- "focus5010d": ("50x10", 50, 10),
380
- "focus3010d": ("30x10", 30, 10),
381
- }
382
- v_mode = caget("BL24I-OP-MFM-01:G0:TARGETAPPLY")
383
- h_mode = caget("BL24I-OP-MFM-01:G1:TARGETAPPLY")
384
- # Validate these and note an error otherwise
385
- if not v_mode.startswith("VMFM") or v_mode[4:] not in focus_modes:
386
- SSX_LOGGER.error("Unrecognised vertical beam mode %s", v_mode)
387
- if not h_mode.startswith("HMFM") or h_mode[4:] not in focus_modes:
388
- SSX_LOGGER.error("Unrecognised horizontal beam mode %s", h_mode)
389
- _, h, _ = focus_modes.get(h_mode[4:], (None, None, None))
390
- _, _, v = focus_modes.get(v_mode[4:], (None, None, None))
391
-
392
- return (h, v)
370
+ pilatus_metadata: PilatusMetadata = i24.pilatus_metadata()
371
+
372
+ filename_template = yield from bps.rd(pilatus_metadata.filename_template)
373
+ return filename_template
393
374
 
394
375
 
395
376
  def get_resolution(detector: Detector, distance: float, wavelength: float) -> float:
396
- """
397
- Calculate the inscribed resolution for detector.
377
+ """ Calculate the inscribed resolution for detector.
398
378
 
399
- This assumes perfectly centered beam as I don't know where to
400
- extract the beam position parameters yet.
379
+ This assumes perfectly centered beam as I don't know where to extract the beam \
380
+ position parameters yet.
401
381
 
402
382
  Args:
403
- distance: Distance to detector (mm)
404
- wavelength: Beam wavelength (Å)
383
+ detector (Detector): Detector instance, Eiger() or Pilatus().
384
+ distance (float): Distance to detector, in mm.
385
+ wavelength (float): Beam wavelength, in Å.
405
386
 
406
387
  Returns:
407
- Maximum resolution (Å)
388
+ Maximum resolution, in Å.
408
389
  """
409
390
  width = detector.image_size_mm[0]
410
391
  return round(wavelength / (2 * math.sin(math.atan(width / (2 * distance)) / 2)), 2)
411
-
412
-
413
- def get_beam_center(detector: Detector) -> tuple[float, float]:
414
- """Get the detector beam center, in mm"""
415
- beamX = float(caget(detector.pv.beamx)) * detector.pixel_size_mm[0]
416
- beamY = float(caget(detector.pv.beamy)) * detector.pixel_size_mm[1]
417
- return (beamX, beamY)
@@ -370,7 +370,7 @@ font "arial-medium-r-24.0"
370
370
  buttonLabel "Start"
371
371
  numCmds 1
372
372
  command {
373
- 0 "blueapi -c CONFIG_LOCATION controller run run_extruder_plan '\{\"zebra\":\"zebra\",\"aperture\":\"aperture\",\"backlight\":\"backlight\",\"beamstop\":\"beamstop\",\"detector_stage\":\"detector_motion\",\"shutter\":\"shutter\",\"dcm\":\"dcm\"\}'"
373
+ 0 "blueapi -c CONFIG_LOCATION controller run run_extruder_plan '\{\"zebra\":\"zebra\",\"aperture\":\"aperture\",\"backlight\":\"backlight\",\"beamstop\":\"beamstop\",\"detector_stage\":\"detector_motion\",\"shutter\":\"shutter\",\"dcm\":\"dcm\",\"mirrors\":\"focus_mirrors\",\"attenuator\":\"attenuator\"\}'"
374
374
  }
375
375
  endObjectProperties
376
376