mx-bluesky 1.4.0__py3-none-any.whl → 1.4.1__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 (78) 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/i04/thawing_plan.py +1 -1
  4. mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
  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 +54 -21
  7. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
  8. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +67 -50
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +26 -79
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -199
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +4 -6
  13. mx_bluesky/beamlines/i24/serial/log.py +1 -1
  14. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
  15. mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
  16. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
  17. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
  18. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
  19. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +103 -81
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -2
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +24 -26
  22. mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
  23. mx_bluesky/common/external_interaction/config_server.py +46 -0
  24. mx_bluesky/common/parameters/components.py +52 -15
  25. mx_bluesky/common/parameters/constants.py +11 -1
  26. mx_bluesky/common/parameters/gridscan.py +94 -0
  27. mx_bluesky/{hyperion → common}/parameters/robot_load.py +2 -2
  28. mx_bluesky/common/plans/do_fgs.py +2 -2
  29. mx_bluesky/common/utils/log.py +2 -0
  30. mx_bluesky/hyperion/__main__.py +2 -1
  31. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +21 -31
  32. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +4 -4
  33. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -1
  34. mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
  35. mx_bluesky/hyperion/exceptions.py +13 -1
  36. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  37. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  38. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  39. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  40. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +133 -97
  41. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +42 -18
  42. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
  43. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
  44. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +1 -1
  45. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  46. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +36 -17
  47. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -5
  48. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +28 -28
  49. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +64 -16
  50. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +11 -3
  51. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -10
  52. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
  53. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +4 -0
  54. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  55. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
  56. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
  57. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +18 -10
  58. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  59. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  60. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +15 -9
  63. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  64. mx_bluesky/hyperion/external_interaction/config_server.py +8 -37
  65. mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
  66. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
  67. mx_bluesky/hyperion/parameters/components.py +4 -9
  68. mx_bluesky/hyperion/parameters/constants.py +0 -1
  69. mx_bluesky/hyperion/parameters/gridscan.py +33 -76
  70. mx_bluesky/hyperion/parameters/load_centre_collect.py +14 -9
  71. mx_bluesky/hyperion/parameters/rotation.py +15 -6
  72. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/METADATA +35 -34
  73. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +77 -70
  74. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
  75. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -150
  76. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
  77. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
  78. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/top_level.txt +0 -0
@@ -1,32 +1,27 @@
1
1
  import datetime
2
2
  import json
3
- import logging
4
3
  import math
5
4
  import os
6
- import re
7
5
  import subprocess
8
- import warnings
9
6
  from functools import lru_cache
7
+ from typing import Literal
10
8
 
9
+ import bluesky.plan_stubs as bps
11
10
  import requests
12
-
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,
11
+ from dodal.beamlines import i24
12
+ from dodal.devices.i24.beam_center import DetectorBeamCenter
13
+ from dodal.devices.i24.dcm import DCM
14
+ from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
15
+ from dodal.devices.i24.pilatus_metadata import PilatusMetadata
16
+
17
+ from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import PumpProbeSetting
18
+ from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
19
+ from mx_bluesky.beamlines.i24.serial.parameters import (
20
+ BeamSettings,
21
+ ExtruderParameters,
22
+ FixedTargetParameters,
21
23
  )
22
-
23
- try:
24
- from typing import Literal
25
- except ImportError:
26
- pass
27
-
28
- logger = logging.getLogger("I24ssx.DCID")
29
-
24
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import Detector, Eiger, Pilatus
30
25
 
31
26
  # Collection start/end script to kick off analysis
32
27
  COLLECTION_START_SCRIPT = "/dls_sw/i24/scripts/RunAtStartOfCollect-i24-ssx.sh"
@@ -41,7 +36,7 @@ CREDENTIALS_LOCATION = "/scratch/ssx_dcserver.key"
41
36
  def get_auth_header() -> dict:
42
37
  """Read the credentials file and build the Authorisation header"""
43
38
  if not os.path.isfile(CREDENTIALS_LOCATION):
44
- logger.warning(
39
+ SSX_LOGGER.warning(
45
40
  "Could not read %s; attempting to proceed without credentials",
46
41
  CREDENTIALS_LOCATION,
47
42
  )
@@ -51,19 +46,54 @@ def get_auth_header() -> dict:
51
46
  return {"Authorization": "Bearer " + token}
52
47
 
53
48
 
54
- class DCID:
49
+ def read_beam_info_from_hardware(
50
+ dcm: DCM,
51
+ mirrors: FocusMirrorsMode,
52
+ beam_center: DetectorBeamCenter,
53
+ detector_name: Literal["eiger", "pilatus"],
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 (Literal["eiger", "pilatus"]): The detector currently in use.
63
+
64
+ Returns:
65
+ BeamSettings parameter model.
55
66
  """
56
- 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.
57
87
 
58
88
  Args:
59
- server: The URL for the bridge server, if not the default.
60
- emit_errors:
61
- If False, errors while interacting with the DCID server will
62
- not be propagated to the caller. This decides if you want to
63
- stop collection if you can't get a DCID
64
- timeout: Length of time to wait for the DB server before giving up
65
- ssx_type: The type of SSX experiment this is for
66
- 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.
67
97
 
68
98
 
69
99
  Attributes:
@@ -77,47 +107,44 @@ class DCID:
77
107
  server: str | None = None,
78
108
  emit_errors: bool = True,
79
109
  timeout: float = 10,
80
- ssx_type: SSXType = SSXType.FIXED,
81
- detector: Detector | Literal["eiger", "pilatus"] | None = None,
110
+ expt_params: ExtruderParameters | FixedTargetParameters,
82
111
  ):
112
+ self.parameters = expt_params
83
113
  self.detector: Detector
84
114
  # Handle case of string literal
85
- if detector == "eiger":
86
- self.detector = Eiger()
87
- elif detector == "pilatus":
88
- self.detector = Pilatus()
89
- elif detector is None:
90
- self.detector = Pilatus()
91
- warnings.warn(
92
- "Please pass detector= to DCID. Pilatus assumed, this will be removed in the future.",
93
- UserWarning,
94
- stacklevel=5,
95
- )
115
+ match expt_params.detector_name:
116
+ case "eiger":
117
+ self.detector = Eiger()
118
+ case "pilatus":
119
+ self.detector = Pilatus()
96
120
 
97
121
  self.server = server or DEFAULT_ISPYB_SERVER
98
122
  self.emit_errors = emit_errors
99
123
  self.error = False
100
124
  self.timeout = timeout
101
- self.ssx_type = SSXType(ssx_type)
102
125
  self.dcid = None
103
126
 
104
127
  def generate_dcid(
105
128
  self,
106
- visit: str,
129
+ beam_settings: BeamSettings,
107
130
  image_dir: str,
131
+ file_template: str,
108
132
  num_images: int,
109
- exposure_time: float,
110
- start_time: datetime.datetime | None = None,
111
133
  shots_per_position: int = 1,
112
- pump_exposure_time: float | None = None,
113
- pump_delay: float = 0,
114
- pump_status: int = 0,
134
+ start_time: datetime.datetime | None = None,
135
+ pump_probe: bool = False,
115
136
  ):
116
137
  """Generate an ispyb DCID.
117
138
 
118
139
  Args:
119
- visit: The name of the visit e.g. "mx12345-4"
120
- 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.
121
148
  """
122
149
  try:
123
150
  if not start_time:
@@ -125,22 +152,18 @@ class DCID:
125
152
  elif not start_time.timetz:
126
153
  start_time = start_time.astimezone()
127
154
 
128
- # Gather data from the beamline
129
- detector_distance = float(caget(self.detector.pv.detector_distance))
130
- wavelength = float(caget(self.detector.pv.wavelength))
131
- resolution = get_resolution(self.detector, detector_distance, wavelength)
132
- beamsize_x, beamsize_y = get_beamsize()
133
- transmission = float(caget(self.detector.pv.transmission)) * 100
134
- 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
135
163
 
136
164
  if isinstance(self.detector, Pilatus):
137
- # Mirror the construction that the PPU does
138
- fileTemplate = get_pilatus_filename_template_from_pvs()
139
165
  startImageNumber = 0
140
166
  elif isinstance(self.detector, Eiger):
141
- # Eiger base filename is directly written to the PV
142
- # Nexgen then uses this to write the .nxs file
143
- fileTemplate = str(cagetstring(self.detector.pv.file_name)) + ".nxs"
144
167
  startImageNumber = 1
145
168
  else:
146
169
  raise ValueError("Unknown detector:", self.detector)
@@ -149,48 +172,48 @@ class DCID:
149
172
  {
150
173
  "name": "Xray probe",
151
174
  "offset": 0,
152
- "duration": exposure_time,
153
- "period": exposure_time,
175
+ "duration": self.parameters.exposure_time_s,
176
+ "period": self.parameters.exposure_time_s,
154
177
  "repetition": shots_per_position,
155
178
  "eventType": "XrayDetection",
156
179
  }
157
180
  ]
158
- if pump_status > 0:
159
- # https://confluence.diamond.ac.uk/pages/viewpage.action?pageId=131238829
160
- # https://confluence.diamond.ac.uk/display/MXTech/Dynamics+and+fixed+targets
161
- # pump_status = 0: no pump probe
162
- # pump_status = 1: pump then probe
163
- # pump_status = 2: pump within probe
164
- # pump_status = 3-7: different EAVA modes (i.e. also pump then probe)
165
- if pump_status != 2 and self.ssx_type is SSXType.FIXED:
166
- # Pump status could be 1 for extruder but not have this.
167
- # pump then probe - pump_delay corresponds to time *before* first image
168
- 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
169
193
  events.append(
170
194
  {
171
195
  "name": "Laser probe",
172
196
  "offset": pump_delay,
173
- "duration": pump_exposure_time,
174
- # "period": None,
197
+ "duration": self.parameters.laser_dwell_s,
175
198
  "repetition": 1,
176
199
  "eventType": "LaserExcitation",
177
200
  },
178
201
  )
179
202
 
180
203
  data = {
181
- "detectorDistance": float(detector_distance),
204
+ "detectorDistance": self.parameters.detector_distance_mm,
182
205
  "detectorId": self.detector.id,
183
- "exposureTime": float(exposure_time),
184
- "fileTemplate": fileTemplate,
185
- "imageDirectory": str(image_dir),
186
- "numberOfImages": int(num_images),
187
- "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,
188
211
  "startImageNumber": startImageNumber,
189
212
  "startTime": start_time.isoformat(),
190
- "transmission": float(transmission),
191
- "visit": visit,
192
- "wavelength": float(wavelength),
193
- "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},
194
217
  "xBeam": xbeam,
195
218
  "yBeam": ybeam,
196
219
  "ssx": {
@@ -205,12 +228,12 @@ class DCID:
205
228
 
206
229
  # Log what we are doing here
207
230
  try:
208
- logger.info(
231
+ SSX_LOGGER.info(
209
232
  "BRIDGE: POST /dc --data %s",
210
233
  repr(json.dumps(data)),
211
234
  )
212
235
  except Exception:
213
- logger.info(
236
+ SSX_LOGGER.info(
214
237
  "Caught exception converting data to JSON. Data:\n%s\nVERBOSE:\n%s",
215
238
  str({k: type(v) for k, v in data.items()}),
216
239
  )
@@ -224,20 +247,20 @@ class DCID:
224
247
  )
225
248
  resp.raise_for_status()
226
249
  self.dcid = resp.json()["dataCollectionId"]
227
- logger.info("Generated DCID %s", self.dcid)
250
+ SSX_LOGGER.info("Generated DCID %s", self.dcid)
228
251
  except requests.HTTPError as e:
229
252
  self.error = True
230
- logger.error(
253
+ SSX_LOGGER.error(
231
254
  "DCID generation Failed; Reason from server: %s", e.response.text
232
255
  )
233
256
  if self.emit_errors:
234
257
  raise
235
- logger.exception("Error generating DCID: %s", e)
258
+ SSX_LOGGER.exception("Error generating DCID: %s", e)
236
259
  except Exception as e:
237
260
  self.error = True
238
261
  if self.emit_errors:
239
262
  raise
240
- logger.exception("Error generating DCID: %s", e)
263
+ SSX_LOGGER.exception("Error generating DCID: %s", e)
241
264
 
242
265
  def __int__(self):
243
266
  return self.dcid
@@ -248,13 +271,13 @@ class DCID:
248
271
  return None
249
272
  try:
250
273
  command = [COLLECTION_START_SCRIPT, str(self.dcid)]
251
- logger.info("Running %s", " ".join(command))
274
+ SSX_LOGGER.info("Running %s", " ".join(command))
252
275
  subprocess.Popen(command)
253
276
  except Exception as e:
254
277
  self.error = True
255
278
  if self.emit_errors:
256
279
  raise
257
- logger.warning("Error starting start of collect script: %s", e)
280
+ SSX_LOGGER.warning("Error starting start of collect script: %s", e)
258
281
 
259
282
  def notify_end(self):
260
283
  """Send notifications that the collection has now ended"""
@@ -262,13 +285,13 @@ class DCID:
262
285
  return
263
286
  try:
264
287
  command = [COLLECTION_END_SCRIPT, str(self.dcid)]
265
- logger.info("Running %s", " ".join(command))
288
+ SSX_LOGGER.info("Running %s", " ".join(command))
266
289
  subprocess.Popen(command)
267
290
  except Exception as e:
268
291
  self.error = True
269
292
  if self.emit_errors:
270
293
  raise
271
- logger.warning("Error running end of collect notification: %s", e)
294
+ SSX_LOGGER.warning("Error running end of collect notification: %s", e)
272
295
 
273
296
  def collection_complete(
274
297
  self, end_time: str | datetime.datetime | None = None, aborted: bool = False
@@ -285,7 +308,7 @@ class DCID:
285
308
  # end_time might be a string from time.ctime
286
309
  if isinstance(end_time, str):
287
310
  end_time = datetime.datetime.strptime(end_time, "%a %b %d %H:%M:%S %Y")
288
- logger.debug("Parsed end time: %s", end_time)
311
+ SSX_LOGGER.debug("Parsed end time: %s", end_time)
289
312
 
290
313
  if not end_time:
291
314
  end_time = datetime.datetime.now().astimezone()
@@ -302,13 +325,13 @@ class DCID:
302
325
  if self.dcid is None:
303
326
  # Print what we would have sent. This means that if something is failing,
304
327
  # we still have the data to upload in the log files.
305
- logger.info(
328
+ SSX_LOGGER.info(
306
329
  'BRIDGE: No DCID but Would PATCH "/dc/XXXX" --data=%s',
307
330
  repr(json.dumps(data)),
308
331
  )
309
332
  return
310
333
 
311
- logger.info(
334
+ SSX_LOGGER.info(
312
335
  'BRIDGE: PATCH "/dc/%s" --data=%s', self.dcid, repr(json.dumps(data))
313
336
  )
314
337
  response = requests.patch(
@@ -318,7 +341,7 @@ class DCID:
318
341
  headers=get_auth_header(),
319
342
  )
320
343
  response.raise_for_status()
321
- logger.info("Successfully updated end time for DCID %d", self.dcid)
344
+ SSX_LOGGER.info("Successfully updated end time for DCID %d", self.dcid)
322
345
  except Exception as e:
323
346
  resp_obj = getattr(e, "response", None)
324
347
  try:
@@ -333,87 +356,36 @@ class DCID:
333
356
  self.error = True
334
357
  if self.emit_errors:
335
358
  raise
336
- logger.warning("Error completing DCID: %s (%s)", e, resp_str)
359
+ SSX_LOGGER.warning("Error completing DCID: %s (%s)", e, resp_str)
337
360
 
338
361
 
339
- def get_pilatus_filename_template_from_pvs() -> str:
340
- """
341
- Get the template file path by querying the detector PVs.
342
-
343
- Returns: A template string, with the image numbers replaced with '#'
362
+ def get_pilatus_filename_template_from_device():
344
363
  """
345
-
346
- filename = cagetstring(pv.pilat_filename)
347
- filename_template = cagetstring(pv.pilat_filetemplate)
348
- file_number = int(caget(pv.pilat_filenumber))
349
- # Exploit fact that passing negative numbers will put the - before the 0's
350
- expected_filename = str(filename_template % (filename, f"{file_number:05d}_", -9))
351
- # Now, find the -09 part of this
352
- numberpart = re.search(r"(-0+9)", expected_filename)
353
- # Make sure this was the only one
354
- if numberpart is not None:
355
- assert re.search(r"(-0+9)", expected_filename[numberpart.end() :]) is None
356
- template_fill = "#" * len(numberpart.group(0))
357
- return (
358
- expected_filename[: numberpart.start()]
359
- + template_fill
360
- + expected_filename[numberpart.end() :]
361
- )
362
- else:
363
- raise ValueError(f"{filename=} did not contain the numbers for templating")
364
-
365
-
366
- def get_beamsize() -> tuple[float | None, float | None]:
367
- """
368
- 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.
369
366
 
370
367
  Returns:
371
- A tuple (x, y) of beam size (in µm). These values can be 'None'
372
- if the focus mode was unrecognised.
368
+ A template string, with the image numbers replaced with '#'
373
369
  """
374
- # These I24 modes are from GDA
375
- focus_modes = {
376
- "focus10": ("7x7", 7, 7),
377
- "focus20d": ("20x20", 20, 20),
378
- "focus30d": ("30x30", 30, 30),
379
- "focus50d": ("50x50", 50, 50),
380
- "focus1050d": ("10x50", 10, 50),
381
- "focus5010d": ("50x10", 50, 10),
382
- "focus3010d": ("30x10", 30, 10),
383
- }
384
- v_mode = caget("BL24I-OP-MFM-01:G0:TARGETAPPLY")
385
- h_mode = caget("BL24I-OP-MFM-01:G1:TARGETAPPLY")
386
- # Validate these and note an error otherwise
387
- if not v_mode.startswith("VMFM") or v_mode[4:] not in focus_modes:
388
- logger.error("Unrecognised vertical beam mode %s", v_mode)
389
- if not h_mode.startswith("HMFM") or h_mode[4:] not in focus_modes:
390
- logger.error("Unrecognised horizontal beam mode %s", h_mode)
391
- _, h, _ = focus_modes.get(h_mode[4:], (None, None, None))
392
- _, _, v = focus_modes.get(v_mode[4:], (None, None, None))
393
-
394
- 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
395
374
 
396
375
 
397
376
  def get_resolution(detector: Detector, distance: float, wavelength: float) -> float:
398
- """
399
- Calculate the inscribed resolution for detector.
377
+ """ Calculate the inscribed resolution for detector.
400
378
 
401
- This assumes perfectly centered beam as I don't know where to
402
- 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.
403
381
 
404
382
  Args:
405
- distance: Distance to detector (mm)
406
- 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 Å.
407
386
 
408
387
  Returns:
409
- Maximum resolution (Å)
388
+ Maximum resolution, in Å.
410
389
  """
411
390
  width = detector.image_size_mm[0]
412
391
  return round(wavelength / (2 * math.sin(math.atan(width / (2 * distance)) / 2)), 2)
413
-
414
-
415
- def get_beam_center(detector: Detector) -> tuple[float, float]:
416
- """Get the detector beam center, in mm"""
417
- beamX = float(caget(detector.pv.beamx)) * detector.pixel_size_mm[0]
418
- beamY = float(caget(detector.pv.beamy)) * detector.pixel_size_mm[1]
419
- 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