mx-bluesky 0.0.1__py3-none-any.whl → 0.3.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 (82) hide show
  1. mx_bluesky/__main__.py +1 -2
  2. mx_bluesky/_version.py +14 -2
  3. mx_bluesky/example.py +4 -4
  4. mx_bluesky/i04/__init__.py +3 -0
  5. mx_bluesky/i04/callbacks/murko_callback.py +45 -0
  6. mx_bluesky/i04/thawing_plan.py +84 -0
  7. mx_bluesky/i24/serial/__init__.py +49 -0
  8. mx_bluesky/i24/serial/blueapi_config.yaml +12 -0
  9. mx_bluesky/{I24 → i24}/serial/dcid.py +53 -41
  10. mx_bluesky/{I24 → i24}/serial/extruder/EX-gui-edm/DetStage.edl +1 -2
  11. mx_bluesky/{I24 → i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +28 -32
  12. mx_bluesky/{I24 → i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -1
  13. mx_bluesky/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +513 -0
  14. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +1 -2
  15. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +1 -2
  16. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +46 -41
  17. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -1
  18. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +10 -11
  19. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -1
  20. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -1
  21. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -1
  22. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -1
  23. mx_bluesky/{I24 → i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +300 -119
  24. mx_bluesky/i24/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  25. mx_bluesky/i24/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  26. mx_bluesky/{I24 → i24}/serial/fixed_target/ft_utils.py +24 -1
  27. mx_bluesky/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +798 -0
  28. mx_bluesky/{I24 → i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +374 -408
  29. mx_bluesky/{I24 → i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +34 -40
  30. mx_bluesky/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +325 -0
  31. mx_bluesky/{I24 → i24}/serial/fixed_target/i24ssx_moveonclick.py +40 -45
  32. mx_bluesky/i24/serial/log.py +156 -0
  33. mx_bluesky/i24/serial/parameters/__init__.py +15 -0
  34. mx_bluesky/i24/serial/parameters/constants.py +47 -0
  35. mx_bluesky/i24/serial/parameters/experiment_parameters.py +124 -0
  36. mx_bluesky/i24/serial/parameters/fixed_target/cs/cs_maker.json +9 -0
  37. mx_bluesky/{I24 → i24}/serial/parameters/fixed_target/cs/motor_direction.txt +1 -1
  38. mx_bluesky/{I24 → i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +1 -1
  39. mx_bluesky/i24/serial/parameters/utils.py +40 -0
  40. mx_bluesky/i24/serial/run_extruder.sh +19 -0
  41. mx_bluesky/i24/serial/run_fixed_target.sh +22 -0
  42. mx_bluesky/i24/serial/run_serial.py +36 -0
  43. mx_bluesky/{I24 → i24}/serial/set_visit_directory.sh +6 -1
  44. mx_bluesky/{I24 → i24}/serial/setup_beamline/pv.py +1 -62
  45. mx_bluesky/{I24 → i24}/serial/setup_beamline/pv_abstract.py +6 -7
  46. mx_bluesky/{I24 → i24}/serial/setup_beamline/setup_beamline.py +90 -269
  47. mx_bluesky/{I24 → i24}/serial/setup_beamline/setup_detector.py +47 -40
  48. mx_bluesky/i24/serial/setup_beamline/setup_zebra_plans.py +459 -0
  49. mx_bluesky/i24/serial/start_blueapi.sh +28 -0
  50. mx_bluesky/i24/serial/write_nexus.py +102 -0
  51. mx_bluesky/parameters/__init__.py +31 -0
  52. mx_bluesky/parameters/components.py +200 -0
  53. {mx_bluesky-0.0.1.dist-info → mx_bluesky-0.3.1.dist-info}/METADATA +37 -26
  54. mx_bluesky-0.3.1.dist-info/RECORD +67 -0
  55. {mx_bluesky-0.0.1.dist-info → mx_bluesky-0.3.1.dist-info}/WHEEL +1 -1
  56. mx_bluesky-0.3.1.dist-info/entry_points.txt +4 -0
  57. mx_bluesky/I24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +0 -476
  58. mx_bluesky/I24/serial/fixed_target/FT-gui-edm/ME14E-motors.edl +0 -1874
  59. mx_bluesky/I24/serial/fixed_target/__init__.py +0 -0
  60. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +0 -695
  61. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -463
  62. mx_bluesky/I24/serial/log.py +0 -101
  63. mx_bluesky/I24/serial/parameters/__init__.py +0 -5
  64. mx_bluesky/I24/serial/parameters/constants.py +0 -39
  65. mx_bluesky/I24/serial/parameters/fixed_target/cs/cs_maker.json +0 -9
  66. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_1.txt +0 -4
  67. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_2.txt +0 -4
  68. mx_bluesky/I24/serial/parameters/fixed_target/litemaps/currentchip.map +0 -81
  69. mx_bluesky/I24/serial/parameters/fixed_target/parameters.txt +0 -13
  70. mx_bluesky/I24/serial/run_serial.py +0 -52
  71. mx_bluesky/I24/serial/write_nexus.py +0 -113
  72. mx_bluesky-0.0.1.dist-info/RECORD +0 -58
  73. mx_bluesky-0.0.1.dist-info/entry_points.txt +0 -4
  74. /mx_bluesky/{I24 → i24}/__init__.py +0 -0
  75. /mx_bluesky/{I24/serial → i24/serial/extruder}/__init__.py +0 -0
  76. /mx_bluesky/{I24/serial/extruder → i24/serial/fixed_target}/__init__.py +0 -0
  77. /mx_bluesky/{I24 → i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  78. /mx_bluesky/{I24 → i24}/serial/run_ssx.sh +0 -0
  79. /mx_bluesky/{I24 → i24}/serial/setup_beamline/__init__.py +0 -0
  80. /mx_bluesky/{I24 → i24}/serial/setup_beamline/ca.py +0 -0
  81. {mx_bluesky-0.0.1.dist-info → mx_bluesky-0.3.1.dist-info}/LICENSE +0 -0
  82. {mx_bluesky-0.0.1.dist-info → mx_bluesky-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,459 @@
1
+ """
2
+ Zebra setup plans for extruder and fastchip serial collections.
3
+
4
+ For clarification on the Zebra setup in either use case, please see
5
+ https://confluence.diamond.ac.uk/display/MXTech/Zebra+settings+I24
6
+
7
+ Note on soft inputs. In the code, soft inputs are 1 indexed following the numbering on
8
+ the edm screen, while on the schematics they are 0 indexed. Thus, `Soft In 1` from the
9
+ schematics corresponds to soft_in_2 in the code.
10
+ """
11
+
12
+ import logging
13
+
14
+ import bluesky.plan_stubs as bps
15
+ from dodal.devices.zebra import (
16
+ AND3,
17
+ AND4,
18
+ DISCONNECT,
19
+ IN1_TTL,
20
+ IN3_TTL,
21
+ OR1,
22
+ PC_ARM,
23
+ PC_GATE,
24
+ PC_PULSE,
25
+ PULSE1,
26
+ PULSE2,
27
+ SOFT_IN2,
28
+ SOFT_IN3,
29
+ ArmDemand,
30
+ ArmSource,
31
+ I24Axes,
32
+ RotationDirection,
33
+ SoftInState,
34
+ TrigSource,
35
+ Zebra,
36
+ )
37
+
38
+ # Detector specific outs
39
+ TTL_EIGER = 1
40
+ TTL_PILATUS = 2
41
+ TTL_FAST_SHUTTER = 4
42
+
43
+ SHUTTER_MODE = {
44
+ "manual": SoftInState.NO,
45
+ "auto": SoftInState.YES,
46
+ }
47
+
48
+ GATE_START = 1.0
49
+ SHUTTER_OPEN_TIME = 0.05 # For pp with long delays
50
+
51
+ logger = logging.getLogger("I24ssx.setup_zebra")
52
+
53
+
54
+ def get_zebra_settings_for_extruder(
55
+ exp_time: float,
56
+ pump_exp: float,
57
+ pump_delay: float,
58
+ ) -> tuple[float, float]:
59
+ """Calculates and returns gate width and step for extruder collections with pump \
60
+ probe.
61
+
62
+ The gate width is calculated by adding the exposure time, pump exposure and \
63
+ pump delay. From this value, the gate step is obtained by adding a 0.01 buffer to \
64
+ the width. The value of this buffer is empirically determined.
65
+ """
66
+ pump_probe_buffer = 0.01
67
+ gate_width = pump_exp + pump_delay + exp_time
68
+ gate_step = gate_width + pump_probe_buffer
69
+ return gate_width, gate_step
70
+
71
+
72
+ def arm_zebra(zebra: Zebra):
73
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
74
+ logger.info("Zebra armed.")
75
+
76
+
77
+ def disarm_zebra(zebra: Zebra):
78
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
79
+ logger.info("Zebra disarmed.")
80
+
81
+
82
+ def open_fast_shutter(zebra: Zebra):
83
+ yield from bps.abs_set(zebra.inputs.soft_in_2, SoftInState.YES, wait=True)
84
+ logger.info("Fast shutter open.")
85
+
86
+
87
+ def close_fast_shutter(zebra: Zebra):
88
+ yield from bps.abs_set(zebra.inputs.soft_in_2, SoftInState.NO, wait=True)
89
+ logger.info("Fast shutter closed.")
90
+
91
+
92
+ def set_shutter_mode(zebra: Zebra, mode: str):
93
+ # SOFT_IN:B0 has to be disabled for manual mode
94
+ yield from bps.abs_set(zebra.inputs.soft_in_1, SHUTTER_MODE[mode], wait=True)
95
+ logger.info(f"Shutter mode set to {mode}.")
96
+
97
+
98
+ def setup_pc_sources(
99
+ zebra: Zebra,
100
+ gate_source: TrigSource,
101
+ pulse_source: TrigSource,
102
+ group: str = "pc_sources",
103
+ ):
104
+ yield from bps.abs_set(zebra.pc.gate_source, gate_source, group=group)
105
+ yield from bps.abs_set(zebra.pc.pulse_source, pulse_source, group=group)
106
+ yield from bps.wait(group)
107
+
108
+
109
+ def setup_zebra_for_quickshot_plan(
110
+ zebra: Zebra,
111
+ exp_time: float,
112
+ num_images: int,
113
+ group: str = "setup_zebra_for_quickshot",
114
+ wait: bool = True,
115
+ ):
116
+ """Set up the zebra for a static extruder experiment.
117
+
118
+ Gate source set to 'External' and Pulse source set to 'Time'.
119
+ The gate start is set to 1.0 and the gate width is calculated from \
120
+ exposure time*number of images plus a 0.5 buffer.
121
+
122
+ Args:
123
+ zebra (Zebra): The zebra ophyd device.
124
+ exp_time (float): Collection exposure time, in s.
125
+ num_images (float): Number of images to be collected.
126
+ """
127
+ logger.info("Setup ZEBRA for quickshot collection.")
128
+ yield from bps.abs_set(zebra.pc.arm_source, ArmSource.SOFT, group=group)
129
+ yield from setup_pc_sources(zebra, TrigSource.TIME, TrigSource.EXTERNAL)
130
+
131
+ gate_width = exp_time * num_images + 0.5
132
+ logger.info(f"Gate start set to {GATE_START}, with width {gate_width}.")
133
+ yield from bps.abs_set(zebra.pc.gate_start, GATE_START, group=group)
134
+ yield from bps.abs_set(zebra.pc.gate_width, gate_width, group=group)
135
+
136
+ yield from bps.abs_set(zebra.pc.gate_input, SOFT_IN2, group=group)
137
+ yield from bps.sleep(0.1)
138
+
139
+ if wait:
140
+ yield from bps.wait(group)
141
+ logger.info("Finished setting up zebra.")
142
+
143
+
144
+ def set_logic_gates_for_porto_triggering(
145
+ zebra: Zebra, group: str = "porto_logic_gates"
146
+ ):
147
+ # To OUT2_TTL
148
+ yield from bps.abs_set(
149
+ zebra.logic_gates.and_gates[3].sources[1], SOFT_IN2, group=group
150
+ )
151
+ yield from bps.abs_set(
152
+ zebra.logic_gates.and_gates[3].sources[2], PULSE1, group=group
153
+ )
154
+ # To OUT1_TTL
155
+ yield from bps.abs_set(
156
+ zebra.logic_gates.and_gates[4].sources[1], SOFT_IN2, group=group
157
+ )
158
+ yield from bps.abs_set(
159
+ zebra.logic_gates.and_gates[4].sources[2], PULSE2, group=group
160
+ )
161
+ yield from bps.wait(group=group)
162
+
163
+
164
+ def setup_zebra_for_extruder_with_pump_probe_plan(
165
+ zebra: Zebra,
166
+ det_type: str,
167
+ exp_time: float,
168
+ num_images: int,
169
+ pump_exp: float | None,
170
+ pump_delay: float | None,
171
+ pulse1_delay: float = 0.0,
172
+ group: str = "setup_zebra_for_extruder_pp",
173
+ wait: bool = True,
174
+ ):
175
+ """Zebra setup for extruder pump probe experiment using PORTO laser triggering.
176
+
177
+ For this use case, both the laser and detector set up is taken care of by the Zebra.
178
+ WARNING. This means that some hardware changes have been made.
179
+ Because all four of the zebra ttl outputs are in use in this mode, when the \
180
+ detector in use is the Eiger, the Pilatus cable is repurposed to trigger the light \
181
+ source, and viceversa.
182
+
183
+ The data collection output is OUT1_TTL for Eiger and OUT2_TTL for Pilatus and \
184
+ should be set to AND3.
185
+
186
+ Position compare settings:
187
+ - The gate input is on SOFT_IN2.
188
+ - The number of gates should be equal to the number of images to collect.
189
+ - Gate source set to 'Time' and Pulse source set to 'External'.
190
+
191
+ Pulse output settings:
192
+ - Pulse1 is the laser control on the Zebra. It is set with a 0.0 delay and a \
193
+ width equal to the requested laser dwell.
194
+ - Pulse2 is the detector control. It is set with a delay equal to the laser \
195
+ delay and a width equal to the exposure time.
196
+
197
+ Args:
198
+ zebra (Zebra): The zebra ophyd device.
199
+ det_type (str): Detector in use, current choices are Eiger or Pilatus.
200
+ exp_time (float): Collection exposure time, in s.
201
+ num_images (int): Number of images to be collected.
202
+ pump_exp (float): Laser dwell, in s.
203
+ pump_delay (float): Laser delay, in s.
204
+ pulse1_delay (float, optional): Delay to start pulse1 (the laser control) after \
205
+ gate start. Defaults to 0.0.
206
+ """
207
+ logger.info("Setup ZEBRA for pump probe extruder collection.")
208
+
209
+ yield from set_shutter_mode(zebra, "manual")
210
+
211
+ # Set gate to "Time" and pulse source to "External"
212
+ yield from setup_pc_sources(zebra, TrigSource.TIME, TrigSource.EXTERNAL)
213
+
214
+ # Logic gates
215
+ yield from set_logic_gates_for_porto_triggering(zebra)
216
+
217
+ # Set TTL out depending on detector type
218
+ DET_TTL = TTL_EIGER if det_type == "eiger" else TTL_PILATUS
219
+ LASER_TTL = TTL_PILATUS if det_type == "eiger" else TTL_EIGER
220
+ yield from bps.abs_set(zebra.output.out_pvs[DET_TTL], AND4, group=group)
221
+ yield from bps.abs_set(zebra.output.out_pvs[LASER_TTL], AND3, group=group)
222
+
223
+ yield from bps.abs_set(zebra.pc.gate_input, SOFT_IN2, group=group)
224
+
225
+ assert pump_exp and pump_delay, "Must supply pump_exp and pump_delay!"
226
+ gate_width, gate_step = get_zebra_settings_for_extruder(
227
+ exp_time, pump_exp, pump_delay
228
+ )
229
+ logger.info(
230
+ f"""
231
+ Gate start set to {GATE_START}, with calculated width {gate_width}
232
+ and step {gate_step}.
233
+ """
234
+ )
235
+ yield from bps.abs_set(zebra.pc.gate_start, GATE_START, group=group)
236
+ yield from bps.abs_set(zebra.pc.gate_width, gate_width, group=group)
237
+ yield from bps.abs_set(zebra.pc.gate_step, gate_step, group=group)
238
+ # Number of gates is the same as the number of images
239
+ yield from bps.abs_set(zebra.pc.num_gates, num_images, group=group)
240
+
241
+ # Settings for extruder pump probe:
242
+ # PULSE1_DLY is the start (0 usually), PULSE1_WID is the laser dwell set on edm
243
+ # PULSE2_DLY is the laser delay set on edm, PULSE2_WID is the exposure time
244
+ logger.info(
245
+ f"Pulse1 starting at {pulse1_delay} with width set to laser dwell {pump_exp}."
246
+ )
247
+ yield from bps.abs_set(zebra.output.pulse_1.input, PC_GATE, group=group)
248
+ yield from bps.abs_set(zebra.output.pulse_1.delay, pulse1_delay, group=group)
249
+ yield from bps.abs_set(zebra.output.pulse_1.width, pump_exp, group=group)
250
+ logger.info(
251
+ f"""
252
+ Pulse2 starting at laser delay {pump_delay} with width set to \
253
+ exposure time {exp_time}.
254
+ """
255
+ )
256
+ yield from bps.abs_set(zebra.output.pulse_2.input, PC_GATE, group=group)
257
+ yield from bps.abs_set(zebra.output.pulse_2.delay, pump_delay, group=group)
258
+ yield from bps.abs_set(zebra.output.pulse_2.width, exp_time, group=group)
259
+
260
+ if wait:
261
+ yield from bps.wait(group)
262
+ logger.info("Finished setting up zebra.")
263
+
264
+
265
+ def setup_zebra_for_fastchip_plan(
266
+ zebra: Zebra,
267
+ det_type: str,
268
+ num_gates: int,
269
+ num_exposures: int,
270
+ exposure_time: float,
271
+ start_time_offset: float = 0.0,
272
+ group: str = "setup_zebra_for_fastchip",
273
+ wait: bool = True,
274
+ ):
275
+ """Zebra setup for fixed-target triggering.
276
+
277
+ For this use case, the laser set up is taken care of by the geobrick, leaving only \
278
+ the detector side set up to the Zebra.
279
+ The data collection output is OUT1_TTL for Eiger and OUT2_TTL for Pilatus and \
280
+ should be set to AND3.
281
+
282
+ Position compare settings:
283
+ - The gate input is on IN3_TTL.
284
+ - The number of gates should be equal to the number of apertures to collect.
285
+ - Gate source set to 'External' and Pulse source set to 'Time'
286
+ - Trigger source set to the exposure time with a 100us buffer in order to \
287
+ avoid missing any triggers.
288
+ - The trigger width is calculated depending on which detector is in use: the \
289
+ Pilatus only needs the trigger rising edge to collect for a set time, while \
290
+ the Eiger (used here in Externally Interrupter Exposure Series mode) \
291
+ will only collect while the signal is high and will stop once a falling \
292
+ edge is detected. For this reason a square wave pulse width will be set to \
293
+ half the exposure time in the Pilatus case, and to the exposure time minus \
294
+ a small drop (~100um) for the Eiger.
295
+
296
+ Args:
297
+ zebra (Zebra): The zebra ophyd device.
298
+ det_type (str): Detector in use, current choices are Eiger or Pilatus.
299
+ num_gates (int): Number of apertures to visit in a chip.
300
+ num_exposures (int): Number of times data is collected in each aperture.
301
+ exposure_time (float): Exposure time for each shot.
302
+ start_time_offset (float): Delay on the start of the position compare. \
303
+ Defaults to 0.0 (standard chip collection).
304
+ """
305
+ logger.info("Setup ZEBRA for a fixed target collection.")
306
+
307
+ yield from set_shutter_mode(zebra, "manual")
308
+
309
+ yield from setup_pc_sources(zebra, TrigSource.EXTERNAL, TrigSource.TIME)
310
+
311
+ # Logic Gates
312
+ yield from bps.abs_set(
313
+ zebra.logic_gates.and_gates[3].sources[1], SOFT_IN2, group=group
314
+ )
315
+ yield from bps.abs_set(
316
+ zebra.logic_gates.and_gates[3].sources[2], PC_PULSE, group=group
317
+ )
318
+
319
+ yield from bps.abs_set(zebra.pc.gate_input, IN3_TTL, group=group)
320
+
321
+ # Set TTL out depending on detector type
322
+ # And calculate some of the other settings
323
+ if det_type == "eiger":
324
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_EIGER], AND3, group=group)
325
+ if det_type == "pilatus":
326
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_PILATUS], AND3, group=group)
327
+
328
+ # Square wave - needs a small drop to make it work for eiger
329
+ pulse_width = exposure_time - 0.0001 if det_type == "eiger" else exposure_time / 2
330
+
331
+ # 100us buffer needed to avoid missing some of the triggers
332
+ exptime_buffer = exposure_time + 0.0001
333
+
334
+ # Number of gates is the number of windows collected
335
+ yield from bps.abs_set(zebra.pc.num_gates, num_gates, group=group)
336
+
337
+ yield from bps.abs_set(zebra.pc.pulse_start, start_time_offset, group=group)
338
+ yield from bps.abs_set(zebra.pc.pulse_step, exptime_buffer, group=group)
339
+ yield from bps.abs_set(zebra.pc.pulse_width, pulse_width, group=group)
340
+ yield from bps.abs_set(zebra.pc.pulse_max, num_exposures, group=group)
341
+
342
+ if wait:
343
+ yield from bps.wait(group)
344
+ logger.info("Finished setting up zebra.")
345
+
346
+
347
+ def open_fast_shutter_at_each_position_plan(
348
+ zebra: Zebra,
349
+ num_exposures: int,
350
+ exposure_time: float,
351
+ group: str = "fast_shutter_control",
352
+ wait: bool = True,
353
+ ):
354
+ """A plan to control the fast shutter so that it will open at each position.
355
+ This plan is a specific setup for pump probe fixed target triggering with long \
356
+ delays between exposures.
357
+
358
+ For this use case, the fast shutter opens and closes at every position to avoid \
359
+ destroying the crystals by exposing them to the beam for a long time in between \
360
+ collections.
361
+
362
+ The shutter opening time, hardcoded to 0.05, has been empirically determined.
363
+
364
+ Fast shutter (Pulse2) output settings:
365
+ - Output is OUT4_TTL set to PULSE2.
366
+ - Pulse2 is set with a delay equal to 0 and a width equal to the exposure time \
367
+ multiplied by the number of exposures, plus the shutter opening time.
368
+
369
+ Args:
370
+ zebra (Zebra): The zebra ophyd device.
371
+ num_exposures (int): Number of times data is collected in each aperture.
372
+ exposure_time (float): Exposure time for each shot.
373
+ """
374
+ logger.info(
375
+ "ZEBRA setup for fastchip collection with long delays between exposures."
376
+ )
377
+ logger.debug("Controlling the fast shutter on PULSE2.")
378
+ # Output panel pulse_2 settings
379
+ yield from bps.abs_set(zebra.output.pulse_2.input, PC_GATE, group=group)
380
+ yield from bps.abs_set(zebra.output.pulse_2.delay, 0.0, group=group)
381
+ pulse2_width = num_exposures * exposure_time + SHUTTER_OPEN_TIME
382
+ yield from bps.abs_set(zebra.output.pulse_2.width, pulse2_width, group=group)
383
+
384
+ # Fast shutter
385
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_FAST_SHUTTER], PULSE2, group=group)
386
+
387
+ if wait:
388
+ yield from bps.wait(group=group)
389
+ logger.debug("Finished setting up for long delays.")
390
+
391
+
392
+ def reset_pc_gate_and_pulse(zebra: Zebra, group: str = "reset_pc"):
393
+ yield from bps.abs_set(zebra.pc.gate_start, 0, group=group)
394
+ yield from bps.abs_set(zebra.pc.pulse_width, 0, group=group)
395
+ yield from bps.abs_set(zebra.pc.pulse_step, 0, group=group)
396
+ yield from bps.wait(group=group)
397
+
398
+
399
+ def reset_output_panel(zebra: Zebra, group: str = "reset_zebra_outputs"):
400
+ # Reset TTL out
401
+ yield from bps.abs_set(zebra.output.out_pvs[2], PC_GATE, group=group)
402
+ yield from bps.abs_set(zebra.output.out_pvs[3], DISCONNECT, group=group)
403
+ yield from bps.abs_set(zebra.output.out_pvs[4], OR1, group=group)
404
+
405
+ yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
406
+ yield from bps.abs_set(zebra.output.pulse_2.input, DISCONNECT, group=group)
407
+
408
+ yield from bps.wait(group=group)
409
+
410
+
411
+ def zebra_return_to_normal_plan(
412
+ zebra: Zebra, group: str = "zebra-return-to-normal", wait: bool = True
413
+ ):
414
+ """A plan to reset the Zebra settings at the end of a collection.
415
+
416
+ This plan should only be run after disarming the Zebra.
417
+ """
418
+ yield from bps.abs_set(zebra.pc.reset, 1, group=group)
419
+
420
+ # Reset PC_GATE and PC_SOURCE to "Position"
421
+ yield from setup_pc_sources(zebra, TrigSource.POSITION, TrigSource.POSITION)
422
+
423
+ yield from bps.abs_set(zebra.pc.gate_input, SOFT_IN3, group=group)
424
+ yield from bps.abs_set(zebra.pc.num_gates, 1, group=group)
425
+ yield from bps.abs_set(zebra.pc.pulse_input, DISCONNECT, group=group)
426
+
427
+ # Logic Gates
428
+ yield from bps.abs_set(
429
+ zebra.logic_gates.and_gates[3].sources[1], PC_ARM, group=group
430
+ )
431
+ yield from bps.abs_set(
432
+ zebra.logic_gates.and_gates[3].sources[2], IN1_TTL, group=group
433
+ )
434
+
435
+ # Reset TTL out
436
+ yield from reset_output_panel(zebra)
437
+
438
+ # Reset Pos Trigger and direction to rotation axis ("omega") and positive
439
+ yield from bps.abs_set(zebra.pc.gate_trigger, I24Axes.OMEGA.value, group=group)
440
+ yield from bps.abs_set(zebra.pc.dir, RotationDirection.POSITIVE, group=group)
441
+
442
+ #
443
+ yield from reset_pc_gate_and_pulse(zebra)
444
+
445
+ if wait:
446
+ yield from bps.wait(group)
447
+ logger.info("Zebra settings back to normal.")
448
+
449
+
450
+ def reset_zebra_when_collection_done_plan(zebra: Zebra):
451
+ """
452
+ End of collection zebra operations: close fast shutter, disarm and reset settings.
453
+ """
454
+ logger.debug("Close the fast shutter.")
455
+ yield from close_fast_shutter(zebra)
456
+ logger.debug("Disarm the zebra.")
457
+ yield from disarm_zebra(zebra)
458
+ logger.debug("Set zebra back to normal.")
459
+ yield from zebra_return_to_normal_plan(zebra, wait=True)
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+
3
+ # Get the directory of this script
4
+ current=$( realpath "$( dirname "$0" )" )
5
+
6
+ # Start the blueapi worker using the config file in this module
7
+ echo "Starting the blueapi runner"
8
+ blueapi -c "${current}/blueapi_config.yaml" serve &
9
+
10
+ # Wait for blueapi to start
11
+ for i in {1..30}
12
+ do
13
+ echo "$(date)"
14
+ curl --head -X GET http://localhost:25565/status >/dev/null
15
+ ret_value=$?
16
+ if [ $ret_value -ne 0 ]; then
17
+ sleep 1
18
+ else
19
+ break
20
+ fi
21
+ done
22
+
23
+ if [ $ret_value -ne 0 ]; then
24
+ echo "$(date) BLUEAPI Failed to start!!!!"
25
+ exit 1
26
+ else
27
+ echo "$(date) BLUEAPI started"
28
+ fi
@@ -0,0 +1,102 @@
1
+ import logging
2
+ import os
3
+ import pathlib
4
+ import pprint
5
+ import time
6
+ from datetime import datetime
7
+ from typing import Literal
8
+
9
+ import requests
10
+
11
+ from mx_bluesky.i24.serial.fixed_target.ft_utils import ChipType, MappingType
12
+ from mx_bluesky.i24.serial.parameters import ExtruderParameters, FixedTargetParameters
13
+ from mx_bluesky.i24.serial.setup_beamline import Eiger, caget, cagetstring
14
+
15
+ logger = logging.getLogger("I24ssx.nexus_writer")
16
+
17
+
18
+ def call_nexgen(
19
+ chip_prog_dict: dict | None,
20
+ start_time: datetime,
21
+ parameters: ExtruderParameters | FixedTargetParameters,
22
+ wavelength: float,
23
+ expt_type: Literal["fixed-target", "extruder"] = "fixed-target",
24
+ ):
25
+ det_type = parameters.detector_name
26
+ print(f"det_type: {det_type}")
27
+
28
+ current_chip_map = None
29
+ if expt_type == "fixed-target" and isinstance(parameters, FixedTargetParameters):
30
+ if not (
31
+ parameters.map_type == MappingType.NoMap
32
+ or parameters.chip.chip_type == ChipType.Custom
33
+ ):
34
+ # NOTE Nexgen server is still on nexgen v0.7.2 (fully working for ssx)
35
+ # Will need to be updated, for correctness sake map needs to be None.
36
+ current_chip_map = "/dls_sw/i24/scripts/fastchips/litemaps/currentchip.map"
37
+ pump_status = bool(parameters.pump_repeat)
38
+ total_numb_imgs = parameters.total_num_images
39
+ elif expt_type == "extruder" and isinstance(parameters, ExtruderParameters):
40
+ # chip_prog_dict should be None for extruder (passed as input for now)
41
+ total_numb_imgs = parameters.num_images
42
+ pump_status = parameters.pump_status
43
+ else:
44
+ raise ValueError(f"{expt_type=} not recognised")
45
+
46
+ filename_prefix = cagetstring(Eiger.pv.filenameRBV)
47
+ meta_h5 = (
48
+ pathlib.Path(parameters.visit)
49
+ / parameters.directory
50
+ / f"{filename_prefix}_meta.h5"
51
+ )
52
+ t0 = time.time()
53
+ max_wait = 60 # seconds
54
+ logger.info(f"Watching for {meta_h5}")
55
+ while time.time() - t0 < max_wait:
56
+ if meta_h5.exists():
57
+ logger.info(f"Found {meta_h5} after {time.time() - t0:.1f} seconds")
58
+ time.sleep(5)
59
+ break
60
+ logger.debug(f"Waiting for {meta_h5}")
61
+ time.sleep(1)
62
+ if not meta_h5.exists():
63
+ logger.warning(f"Giving up waiting for {meta_h5} after {max_wait} seconds")
64
+ return False
65
+
66
+ transmission = (float(caget(Eiger.pv.transmission)),)
67
+
68
+ if det_type == Eiger.name:
69
+ bit_depth = int(caget(Eiger.pv.bit_depth))
70
+ logger.debug(
71
+ f"Call to nexgen server with the following chip definition: \n{chip_prog_dict}"
72
+ )
73
+
74
+ access_token = pathlib.Path("/scratch/ssx_nexgen.key").read_text().strip()
75
+ url = "https://ssx-nexgen.diamond.ac.uk/ssx_eiger/write"
76
+ headers = {"Authorization": f"Bearer {access_token}"}
77
+
78
+ payload = {
79
+ "beamline": "i24",
80
+ "beam_center": [caget(Eiger.pv.beamx), caget(Eiger.pv.beamy)],
81
+ "chipmap": current_chip_map,
82
+ "chip_info": chip_prog_dict,
83
+ "det_dist": parameters.detector_distance_mm,
84
+ "exp_time": parameters.exposure_time_s,
85
+ "expt_type": expt_type,
86
+ "filename": filename_prefix,
87
+ "num_imgs": total_numb_imgs,
88
+ "pump_status": pump_status,
89
+ "pump_exp": parameters.laser_dwell_s,
90
+ "pump_delay": parameters.laser_delay_s,
91
+ "transmission": transmission[0],
92
+ "visitpath": os.fspath(meta_h5.parent),
93
+ "wavelength": wavelength,
94
+ "bit_depth": bit_depth,
95
+ }
96
+ logger.info(f"Sending POST request to {url} with payload:")
97
+ logger.info(pprint.pformat(payload))
98
+ response = requests.post(url, headers=headers, json=payload)
99
+ logger.info(f"Response: {response.text} (status code: {response.status_code})")
100
+ # the following will raise an error if the request was unsuccessful
101
+ return response.status_code == requests.codes.ok
102
+ return False
@@ -0,0 +1,31 @@
1
+ from .components import (
2
+ DiffractionExperiment,
3
+ IspybExperimentType,
4
+ OptionalGonioAngleStarts,
5
+ OptionalXyzStarts,
6
+ ParameterVersion,
7
+ RotationAxis,
8
+ SplitScan,
9
+ WithOavCentring,
10
+ WithSample,
11
+ WithScan,
12
+ WithSnapshot,
13
+ XyzAxis,
14
+ XyzStarts,
15
+ )
16
+
17
+ __all__ = [
18
+ "DiffractionExperiment",
19
+ "IspybExperimentType",
20
+ "OptionalGonioAngleStarts",
21
+ "OptionalXyzStarts",
22
+ "ParameterVersion",
23
+ "RotationAxis",
24
+ "SplitScan",
25
+ "WithOavCentring",
26
+ "WithSample",
27
+ "WithScan",
28
+ "WithSnapshot",
29
+ "XyzAxis",
30
+ "XyzStarts",
31
+ ]