mx-bluesky 1.4.6__py3-none-any.whl → 1.4.8__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.
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/aithre_lasershaping/__init__.py +13 -0
- mx_bluesky/beamlines/aithre_lasershaping/check_goniometer_performance.py +29 -0
- mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +18 -0
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +35 -29
- mx_bluesky/beamlines/i04/thawing_plan.py +18 -3
- mx_bluesky/beamlines/i23/__init__.py +3 -0
- mx_bluesky/beamlines/i23/serial.py +71 -0
- mx_bluesky/beamlines/i24/serial/__init__.py +2 -0
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +12 -12
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +36 -30
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +3 -3
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +15 -66
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +8 -10
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +10 -3
- mx_bluesky/beamlines/i24/serial/log.py +9 -9
- mx_bluesky/beamlines/i24/serial/parameters/utils.py +36 -7
- mx_bluesky/beamlines/i24/serial/set_visit_directory.sh +1 -1
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +16 -17
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +4 -4
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +51 -52
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +3 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +9 -7
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +71 -11
- mx_bluesky/beamlines/i24/serial/write_nexus.py +6 -5
- mx_bluesky/{hyperion → common}/device_setup_plans/check_beamstop.py +1 -1
- mx_bluesky/{hyperion → common}/device_setup_plans/manipulate_sample.py +1 -1
- mx_bluesky/{hyperion → common}/device_setup_plans/setup_oav.py +12 -6
- mx_bluesky/common/device_setup_plans/xbpm_feedback.py +45 -0
- mx_bluesky/{hyperion → common}/experiment_plans/change_aperture_then_move_plan.py +13 -29
- mx_bluesky/{hyperion → common}/experiment_plans/oav_grid_detection_plan.py +6 -6
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +8 -9
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +18 -15
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/sample_handling_callback.py +16 -4
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +50 -45
- mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -1
- mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -0
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +18 -2
- mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +4 -4
- mx_bluesky/common/external_interaction/nexus/nexus_utils.py +1 -1
- mx_bluesky/common/parameters/components.py +22 -2
- mx_bluesky/common/parameters/constants.py +6 -16
- mx_bluesky/common/parameters/gridscan.py +36 -32
- mx_bluesky/common/plans/common_flyscan_xray_centre_plan.py +316 -0
- mx_bluesky/common/plans/inner_plans/__init__ .py +0 -0
- mx_bluesky/common/plans/read_hardware.py +3 -3
- mx_bluesky/common/plans/write_sample_status.py +46 -0
- mx_bluesky/common/preprocessors/__init__.py +0 -0
- mx_bluesky/common/preprocessors/preprocessors.py +105 -0
- mx_bluesky/common/protocols/__init__.py +0 -0
- mx_bluesky/common/protocols/protocols.py +10 -0
- mx_bluesky/common/utils/log.py +15 -12
- mx_bluesky/hyperion/__main__.py +5 -24
- mx_bluesky/hyperion/baton_handler.py +84 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +4 -4
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -1
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +0 -33
- mx_bluesky/hyperion/device_setup_plans/utils.py +4 -4
- mx_bluesky/hyperion/experiment_plans/__init__.py +0 -10
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -16
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +71 -88
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +183 -0
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +17 -8
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +29 -8
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +6 -4
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +11 -3
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +9 -34
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +35 -68
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +27 -8
- mx_bluesky/hyperion/external_interaction/agamemnon.py +140 -10
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +17 -9
- mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +259 -0
- mx_bluesky/hyperion/parameters/cli.py +2 -10
- mx_bluesky/hyperion/parameters/constants.py +0 -5
- mx_bluesky/hyperion/parameters/device_composites.py +40 -5
- mx_bluesky/hyperion/parameters/gridscan.py +9 -58
- mx_bluesky/hyperion/parameters/rotation.py +1 -5
- mx_bluesky/hyperion/utils/context.py +2 -5
- mx_bluesky/hyperion/utils/validation.py +13 -10
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/METADATA +10 -9
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/RECORD +92 -79
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/WHEEL +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/aperture_change_callback.py +0 -22
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +0 -103
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +0 -466
- /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short1-laser.png → s1l.png} +0 -0
- /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short2-laser.png → s2l.png} +0 -0
- /mx_bluesky/{hyperion → common}/device_setup_plans/position_detector.py +0 -0
- /mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- /mx_bluesky/common/plans/{do_fgs.py → inner_plans/do_fgs.py} +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info/licenses}/LICENSE +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from math import cos, radians, sin
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from dodal.devices.oav.snapshots.snapshot_image_processing import (
|
|
9
|
+
compute_beam_centre_pixel_xy_for_mm_position,
|
|
10
|
+
draw_crosshair,
|
|
11
|
+
)
|
|
12
|
+
from event_model import Event, EventDescriptor, RunStart
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
|
|
16
|
+
PlanReactiveCallback,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.common.parameters.components import WithSnapshot
|
|
19
|
+
from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
|
|
20
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER as CALLBACK_LOGGER
|
|
21
|
+
|
|
22
|
+
COMPRESSION_LEVEL = 6 # 6 is the default compression level for PIL if not specified
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass
|
|
26
|
+
class _SnapshotInfo:
|
|
27
|
+
beam_centre: tuple[int, int]
|
|
28
|
+
microns_per_pixel: tuple[float, float]
|
|
29
|
+
snapshot_path: str
|
|
30
|
+
omega: int
|
|
31
|
+
sample_pos_mm: tuple[float, float, float]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def snapshot_basename(self) -> str:
|
|
35
|
+
match = re.match("(.*)\\.png", self.snapshot_path)
|
|
36
|
+
assert match, f"Snapshot {self.snapshot_path} was not a .png file"
|
|
37
|
+
return match.groups()[0]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BeamDrawingCallback(PlanReactiveCallback):
|
|
41
|
+
"""
|
|
42
|
+
Callback that monitors for OAV_ROTATION_SNAPSHOT_TRIGGERED events and
|
|
43
|
+
draws a crosshair at the beam centre, saving the snapshot to a file.
|
|
44
|
+
The callback assumes an OAV device "oav" and Smargon "smargon"
|
|
45
|
+
Examples:
|
|
46
|
+
Take a rotation snapshot at the current location
|
|
47
|
+
>>> from bluesky.run_engine import RunEngine
|
|
48
|
+
>>> import bluesky.preprocessors as bpp
|
|
49
|
+
>>> import bluesky.plan_stubs as bps
|
|
50
|
+
>>> from dodal.devices.oav.oav_detector import OAV
|
|
51
|
+
>>> from mx_bluesky.common.parameters.components import WithSnapshot
|
|
52
|
+
>>> def take_snapshot(params: WithSnapshot, oav: OAV, run_engine: RunEngine):
|
|
53
|
+
... run_engine.subscribe(BeamDrawingCallback())
|
|
54
|
+
... @bpp.run_decorator(md={
|
|
55
|
+
... "activate_callbacks": ["BeamDrawingCallback"],
|
|
56
|
+
... "with_snapshot": params.model_dump_json(),
|
|
57
|
+
... })
|
|
58
|
+
... def inner_plan():
|
|
59
|
+
... yield from bps.abs_set(oav.snapshot.directory, "/path/to/snapshot_folder", wait=True)
|
|
60
|
+
... yield from bps.abs_set(oav.snapshot.filename, "my_snapshot_prefix", wait=True)
|
|
61
|
+
... yield from bps.trigger(oav.snapshot, wait=True)
|
|
62
|
+
... yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
|
|
63
|
+
... yield from bps.read(oav)
|
|
64
|
+
... yield from bps.save()
|
|
65
|
+
|
|
66
|
+
Generate rotation snapshots from a previously taken base gridscan snapshot.
|
|
67
|
+
WithSnapshot.snapshot_omegas_deg is ignored and snapshots are generated for the previously captured
|
|
68
|
+
0, 90 base images named "my_snapshot_prefix_0" and "my_snapshot_prefix_90"
|
|
69
|
+
>>> from dodal.devices.smargon import Smargon
|
|
70
|
+
>>> def take_snapshot(params: WithSnapshot, oav: OAV, smargon: Smargon, run_engine: RunEngine):
|
|
71
|
+
... run_engine.subscribe(BeamDrawingCallback())
|
|
72
|
+
... @bpp.run_decorator(md={
|
|
73
|
+
... "activate_callbacks": ["BeamDrawingCallback"],
|
|
74
|
+
... "with_snapshot": params.model_dump_json(),
|
|
75
|
+
... })
|
|
76
|
+
... def inner_plan():
|
|
77
|
+
... for omega in (0, 90,):
|
|
78
|
+
... yield from bps.abs_set(smargon.omega, omega, wait=True)
|
|
79
|
+
... yield from bps.abs_set(oav.grid_snapshot.directory, "/path/to/grid_snapshot_folder", wait=True)
|
|
80
|
+
... yield from bps.abs_set(oav.grid_snapshot.filename, f"my_grid_snapshot_prefix_{omega}", wait=True)
|
|
81
|
+
... yield from bps.trigger(oav.grid_snapshot, wait=True)
|
|
82
|
+
... yield from bps.create(DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED)
|
|
83
|
+
... yield from bps.read(oav) # Capture base image path
|
|
84
|
+
... yield from bps.read(smargon) # Capture base image sample x, y, z, omega
|
|
85
|
+
... yield from bps.save()
|
|
86
|
+
... # Rest of gridscan here...
|
|
87
|
+
... # Later on...
|
|
88
|
+
... for omega in (0, 90,):
|
|
89
|
+
... yield from bps.abs_set(oav.snapshot.last_saved_path,
|
|
90
|
+
... f"/path/to/snapshot_folder/my_snapshot_prefix_{omega}.png", wait=True)
|
|
91
|
+
... yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
|
|
92
|
+
... yield from bps.read(oav) # Capture path info for generated snapshot
|
|
93
|
+
... yield from bps.read(smargon) # Capture the current sample x, y, z
|
|
94
|
+
... yield from bps.save()
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, *args, **kwargs):
|
|
98
|
+
super().__init__(*args, log=CALLBACK_LOGGER, **kwargs)
|
|
99
|
+
self._base_snapshots: list[_SnapshotInfo] = []
|
|
100
|
+
self._rotation_snapshot_descriptor: str = ""
|
|
101
|
+
self._grid_snapshot_descriptor: str = ""
|
|
102
|
+
self._next_snapshot_info: Iterator | None = None
|
|
103
|
+
self._use_grid_snapshots: bool = False
|
|
104
|
+
|
|
105
|
+
def _reset(self):
|
|
106
|
+
self._base_snapshots = []
|
|
107
|
+
|
|
108
|
+
def activity_gated_start(self, doc: RunStart):
|
|
109
|
+
if self.activity_uid == doc.get("uid"):
|
|
110
|
+
self._reset()
|
|
111
|
+
with_snapshot = WithSnapshot.model_validate_json(doc.get("with_snapshot")) # type: ignore
|
|
112
|
+
self._use_grid_snapshots = with_snapshot.use_grid_snapshots
|
|
113
|
+
CALLBACK_LOGGER.info(f"Snapshot callback initialised with {with_snapshot}")
|
|
114
|
+
elif doc.get("subplan_name") == PlanNameConstants.ROTATION_MAIN:
|
|
115
|
+
self._next_snapshot_info = None
|
|
116
|
+
CALLBACK_LOGGER.info("Snapshot callback start rotation")
|
|
117
|
+
return doc
|
|
118
|
+
|
|
119
|
+
def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
120
|
+
if doc.get("name") == DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED:
|
|
121
|
+
self._rotation_snapshot_descriptor = doc["uid"]
|
|
122
|
+
elif doc.get("name") == DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED:
|
|
123
|
+
self._grid_snapshot_descriptor = doc["uid"]
|
|
124
|
+
return doc
|
|
125
|
+
|
|
126
|
+
def activity_gated_event(self, doc: Event) -> Event:
|
|
127
|
+
if doc["descriptor"] == self._rotation_snapshot_descriptor:
|
|
128
|
+
self._handle_rotation_snapshot(doc)
|
|
129
|
+
elif doc["descriptor"] == self._grid_snapshot_descriptor:
|
|
130
|
+
self._handle_grid_snapshot(doc)
|
|
131
|
+
return doc
|
|
132
|
+
|
|
133
|
+
def _extract_base_snapshot_params(
|
|
134
|
+
self, snapshot_device_prefix: str, doc: Event
|
|
135
|
+
) -> _SnapshotInfo:
|
|
136
|
+
data = doc["data"]
|
|
137
|
+
base_snapshot_path = data[f"oav-{snapshot_device_prefix}-last_saved_path"]
|
|
138
|
+
return _SnapshotInfo(
|
|
139
|
+
beam_centre=(data["oav-beam_centre_i"], data["oav-beam_centre_j"]),
|
|
140
|
+
microns_per_pixel=(
|
|
141
|
+
data["oav-microns_per_pixel_x"],
|
|
142
|
+
data["oav-microns_per_pixel_y"],
|
|
143
|
+
),
|
|
144
|
+
snapshot_path=base_snapshot_path,
|
|
145
|
+
sample_pos_mm=(
|
|
146
|
+
data.get("smargon-x", 0.0),
|
|
147
|
+
data.get("smargon-y", 0.0),
|
|
148
|
+
data.get("smargon-z", 0.0),
|
|
149
|
+
),
|
|
150
|
+
omega=round(data.get("smargon-omega", 0.0)),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _handle_grid_snapshot(self, doc: Event):
|
|
154
|
+
snapshot_info = self._extract_base_snapshot_params("grid_snapshot", doc)
|
|
155
|
+
self._base_snapshots.append(snapshot_info)
|
|
156
|
+
|
|
157
|
+
def _handle_rotation_snapshot(self, doc: Event) -> Event:
|
|
158
|
+
data = doc["data"]
|
|
159
|
+
if self._use_grid_snapshots:
|
|
160
|
+
if not self._next_snapshot_info:
|
|
161
|
+
self._next_snapshot_info = iter(self._base_snapshots)
|
|
162
|
+
snapshot_info = next(self._next_snapshot_info, None)
|
|
163
|
+
assert snapshot_info, (
|
|
164
|
+
"Insufficient base gridscan snapshots to generate required rotation snapshots"
|
|
165
|
+
)
|
|
166
|
+
current_sample_pos_mm = (
|
|
167
|
+
data["smargon-x"],
|
|
168
|
+
data["smargon-y"],
|
|
169
|
+
data["smargon-z"],
|
|
170
|
+
)
|
|
171
|
+
CALLBACK_LOGGER.info(
|
|
172
|
+
f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}"
|
|
173
|
+
)
|
|
174
|
+
output_snapshot_directory = data["oav-snapshot-directory"]
|
|
175
|
+
base_file_stem = Path(snapshot_info.snapshot_path).stem
|
|
176
|
+
output_snapshot_filename = _snapshot_filename(base_file_stem)
|
|
177
|
+
output_snapshot_path = (
|
|
178
|
+
f"{output_snapshot_directory}/{output_snapshot_filename}.png"
|
|
179
|
+
)
|
|
180
|
+
self._generate_snapshot_at(
|
|
181
|
+
snapshot_info,
|
|
182
|
+
output_snapshot_path,
|
|
183
|
+
*self._image_plane_offset_mm(snapshot_info, current_sample_pos_mm),
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
snapshot_info = self._extract_base_snapshot_params("snapshot", doc)
|
|
187
|
+
output_snapshot_path = (
|
|
188
|
+
f"{snapshot_info.snapshot_basename}_with_beam_centre.png"
|
|
189
|
+
)
|
|
190
|
+
CALLBACK_LOGGER.info(
|
|
191
|
+
f"Annotating snapshot {output_snapshot_path} from base snapshot {snapshot_info}"
|
|
192
|
+
)
|
|
193
|
+
self._generate_snapshot_zero_offset(
|
|
194
|
+
snapshot_info,
|
|
195
|
+
output_snapshot_path,
|
|
196
|
+
)
|
|
197
|
+
data["oav-snapshot-last_saved_path"] = output_snapshot_path
|
|
198
|
+
return doc
|
|
199
|
+
|
|
200
|
+
def _image_plane_offset_mm(
|
|
201
|
+
self,
|
|
202
|
+
snapshot_info: _SnapshotInfo,
|
|
203
|
+
current_sample_pos_mm: tuple[float, float, float],
|
|
204
|
+
) -> tuple[float, float]:
|
|
205
|
+
return self._project_xyz_to_xy(
|
|
206
|
+
(
|
|
207
|
+
(current_sample_pos_mm[0] - snapshot_info.sample_pos_mm[0]),
|
|
208
|
+
(current_sample_pos_mm[1] - snapshot_info.sample_pos_mm[1]),
|
|
209
|
+
(current_sample_pos_mm[2] - snapshot_info.sample_pos_mm[2]),
|
|
210
|
+
),
|
|
211
|
+
snapshot_info.omega,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _project_xyz_to_xy(
|
|
215
|
+
self, xyz: tuple[float, float, float], omega_deg: float
|
|
216
|
+
) -> tuple[float, float]:
|
|
217
|
+
return (
|
|
218
|
+
xyz[0],
|
|
219
|
+
xyz[1] * cos(-radians(omega_deg)) + xyz[2] * sin(-radians(omega_deg)),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _generate_snapshot_zero_offset(
|
|
223
|
+
self,
|
|
224
|
+
base_snapshot_info: _SnapshotInfo,
|
|
225
|
+
output_snapshot_path: str,
|
|
226
|
+
):
|
|
227
|
+
self._generate_snapshot_at(base_snapshot_info, output_snapshot_path, 0, 0)
|
|
228
|
+
|
|
229
|
+
def _generate_snapshot_at(
|
|
230
|
+
self,
|
|
231
|
+
base_snapshot_info: _SnapshotInfo,
|
|
232
|
+
output_snapshot_path: str,
|
|
233
|
+
image_plane_dx_mm: float,
|
|
234
|
+
image_plane_dy_mm: float,
|
|
235
|
+
):
|
|
236
|
+
"""
|
|
237
|
+
Save a snapshot to the specified path, with an annotated crosshair at the specified
|
|
238
|
+
position
|
|
239
|
+
Args:
|
|
240
|
+
base_snapshot_info: Metadata about the base snapshot image from which the annotated
|
|
241
|
+
image will be derived.
|
|
242
|
+
output_snapshot_path: The path to the image that will be annotated.
|
|
243
|
+
image_plane_dx_mm: Relative x location of the sample to the original image in the image plane (mm)
|
|
244
|
+
image_plane_dy_mm: Relative y location of the sample to the original image in the image plane (mm)
|
|
245
|
+
"""
|
|
246
|
+
image = Image.open(base_snapshot_info.snapshot_path)
|
|
247
|
+
x_px, y_px = compute_beam_centre_pixel_xy_for_mm_position(
|
|
248
|
+
(image_plane_dx_mm, image_plane_dy_mm),
|
|
249
|
+
base_snapshot_info.beam_centre,
|
|
250
|
+
base_snapshot_info.microns_per_pixel,
|
|
251
|
+
)
|
|
252
|
+
draw_crosshair(image, x_px, y_px)
|
|
253
|
+
image.save(output_snapshot_path, format="png", compress_level=COMPRESSION_LEVEL)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _snapshot_filename(grid_snapshot_name):
|
|
257
|
+
time_now = datetime.now()
|
|
258
|
+
filename = f"{time_now.strftime('%H%M%S%f')[:8]}_oav_snapshot_{grid_snapshot_name}"
|
|
259
|
+
return filename
|
|
@@ -9,7 +9,6 @@ from mx_bluesky._version import version
|
|
|
9
9
|
class HyperionArgs:
|
|
10
10
|
dev_mode: bool = False
|
|
11
11
|
verbose_event_logging: bool = False
|
|
12
|
-
skip_startup_connection: bool = False
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
|
|
@@ -17,7 +16,7 @@ def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
|
|
|
17
16
|
parser.add_argument(
|
|
18
17
|
"--dev",
|
|
19
18
|
action="store_true",
|
|
20
|
-
help="Use dev options, such as local graylog instances
|
|
19
|
+
help="Use dev options, such as local graylog instances",
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -32,8 +31,7 @@ def parse_callback_dev_mode_arg() -> bool:
|
|
|
32
31
|
def parse_cli_args() -> HyperionArgs:
|
|
33
32
|
"""Parses all arguments relevant to hyperion. Returns an HyperionArgs dataclass with
|
|
34
33
|
the fields: (verbose_event_logging: bool,
|
|
35
|
-
dev_mode: bool
|
|
36
|
-
skip_startup_connection: bool)"""
|
|
34
|
+
dev_mode: bool)"""
|
|
37
35
|
parser = argparse.ArgumentParser()
|
|
38
36
|
_add_callback_relevant_args(parser)
|
|
39
37
|
parser.add_argument(
|
|
@@ -41,11 +39,6 @@ def parse_cli_args() -> HyperionArgs:
|
|
|
41
39
|
action="store_true",
|
|
42
40
|
help="Log all bluesky event documents to graylog",
|
|
43
41
|
)
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
"--skip-startup-connection",
|
|
46
|
-
action="store_true",
|
|
47
|
-
help="Skip connecting to EPICS PVs on startup",
|
|
48
|
-
)
|
|
49
42
|
parser.add_argument(
|
|
50
43
|
"--version",
|
|
51
44
|
help="Print hyperion version string",
|
|
@@ -56,5 +49,4 @@ def parse_cli_args() -> HyperionArgs:
|
|
|
56
49
|
return HyperionArgs(
|
|
57
50
|
verbose_event_logging=args.verbose_event_logging or False,
|
|
58
51
|
dev_mode=args.dev or False,
|
|
59
|
-
skip_startup_connection=args.skip_startup_connection or False,
|
|
60
52
|
)
|
|
@@ -12,8 +12,6 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
12
12
|
OavConstants,
|
|
13
13
|
PlanGroupCheckpointConstants,
|
|
14
14
|
PlanNameConstants,
|
|
15
|
-
SimConstants,
|
|
16
|
-
TriggerConstants,
|
|
17
15
|
)
|
|
18
16
|
|
|
19
17
|
TEST_MODE = os.environ.get("HYPERION_TEST_MODE")
|
|
@@ -42,15 +40,12 @@ class I03Constants:
|
|
|
42
40
|
@dataclass(frozen=True)
|
|
43
41
|
class HyperionConstants:
|
|
44
42
|
DESCRIPTORS = DocDescriptorNames()
|
|
45
|
-
TRIGGER = TriggerConstants()
|
|
46
43
|
ZOCALO_ENV = EnvironmentConstants.ZOCALO_ENV
|
|
47
44
|
HARDWARE = HardwareConstants()
|
|
48
45
|
I03 = I03Constants()
|
|
49
46
|
PARAM = ExperimentParamConstants()
|
|
50
47
|
PLAN = PlanNameConstants()
|
|
51
48
|
WAIT = PlanGroupCheckpointConstants()
|
|
52
|
-
SIM = SimConstants()
|
|
53
|
-
TRIGGER = TriggerConstants()
|
|
54
49
|
CALLBACK_0MQ_PROXY_PORTS = (5577, 5578)
|
|
55
50
|
DESCRIPTORS = DocDescriptorNames()
|
|
56
51
|
CONFIG_SERVER_URL = (
|
|
@@ -6,13 +6,17 @@ from dodal.devices.aperturescatterguard import (
|
|
|
6
6
|
)
|
|
7
7
|
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
8
8
|
from dodal.devices.backlight import Backlight
|
|
9
|
-
from dodal.devices.
|
|
9
|
+
from dodal.devices.common_dcm import BaseDCM
|
|
10
|
+
from dodal.devices.detector.detector_motion import DetectorMotion
|
|
10
11
|
from dodal.devices.eiger import EigerDetector
|
|
11
12
|
from dodal.devices.fast_grid_scan import (
|
|
12
13
|
PandAFastGridScan,
|
|
13
14
|
ZebraFastGridScan,
|
|
14
15
|
)
|
|
15
16
|
from dodal.devices.flux import Flux
|
|
17
|
+
from dodal.devices.i03 import Beamstop
|
|
18
|
+
from dodal.devices.oav.oav_detector import OAV
|
|
19
|
+
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
16
20
|
from dodal.devices.robot import BartRobot
|
|
17
21
|
from dodal.devices.s4_slit_gaps import S4SlitGaps
|
|
18
22
|
from dodal.devices.smargon import Smargon
|
|
@@ -24,22 +28,53 @@ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
|
|
|
24
28
|
from dodal.devices.zocalo import ZocaloResults
|
|
25
29
|
from ophyd_async.fastcs.panda import HDFPanda
|
|
26
30
|
|
|
31
|
+
from mx_bluesky.common.plans.common_flyscan_xray_centre_plan import (
|
|
32
|
+
FlyScanEssentialDevices,
|
|
33
|
+
)
|
|
34
|
+
|
|
27
35
|
|
|
28
36
|
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
29
|
-
class HyperionFlyScanXRayCentreComposite:
|
|
37
|
+
class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices):
|
|
30
38
|
"""All devices which are directly or indirectly required by this plan"""
|
|
31
39
|
|
|
32
40
|
aperture_scatterguard: ApertureScatterguard
|
|
33
41
|
attenuator: BinaryFilterAttenuator
|
|
42
|
+
dcm: BaseDCM
|
|
43
|
+
eiger: EigerDetector
|
|
44
|
+
flux: Flux
|
|
45
|
+
s4_slit_gaps: S4SlitGaps
|
|
46
|
+
undulator: Undulator
|
|
47
|
+
synchrotron: Synchrotron
|
|
48
|
+
zebra: Zebra
|
|
49
|
+
zocalo: ZocaloResults
|
|
50
|
+
panda: HDFPanda
|
|
51
|
+
panda_fast_grid_scan: PandAFastGridScan
|
|
52
|
+
robot: BartRobot
|
|
53
|
+
sample_shutter: ZebraShutter
|
|
34
54
|
backlight: Backlight
|
|
35
|
-
|
|
55
|
+
xbpm_feedback: XBPMFeedback
|
|
56
|
+
zebra_fast_grid_scan: ZebraFastGridScan
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
60
|
+
class GridDetectThenXRayCentreComposite:
|
|
61
|
+
"""All devices which are directly or indirectly required by this plan"""
|
|
62
|
+
|
|
63
|
+
aperture_scatterguard: ApertureScatterguard
|
|
64
|
+
attenuator: BinaryFilterAttenuator
|
|
65
|
+
backlight: Backlight
|
|
66
|
+
beamstop: Beamstop
|
|
67
|
+
dcm: BaseDCM
|
|
68
|
+
detector_motion: DetectorMotion
|
|
36
69
|
eiger: EigerDetector
|
|
37
70
|
zebra_fast_grid_scan: ZebraFastGridScan
|
|
38
71
|
flux: Flux
|
|
39
|
-
|
|
72
|
+
oav: OAV
|
|
73
|
+
pin_tip_detection: PinTipDetection
|
|
40
74
|
smargon: Smargon
|
|
41
|
-
undulator: Undulator
|
|
42
75
|
synchrotron: Synchrotron
|
|
76
|
+
s4_slit_gaps: S4SlitGaps
|
|
77
|
+
undulator: Undulator
|
|
43
78
|
xbpm_feedback: XBPMFeedback
|
|
44
79
|
zebra: Zebra
|
|
45
80
|
zocalo: ZocaloResults
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dodal.devices.detector import (
|
|
4
|
-
DetectorParams,
|
|
5
|
-
)
|
|
6
3
|
from dodal.devices.fast_grid_scan import (
|
|
7
4
|
PandAGridScanParams,
|
|
8
5
|
ZebraGridScanParams,
|
|
@@ -13,7 +10,6 @@ from mx_bluesky.common.parameters.gridscan import (
|
|
|
13
10
|
SpecifiedThreeDGridScan,
|
|
14
11
|
)
|
|
15
12
|
from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
|
|
16
|
-
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
17
13
|
|
|
18
14
|
|
|
19
15
|
class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
|
|
@@ -23,34 +19,11 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
|
|
|
23
19
|
# https://github.com/DiamondLightSource/hyperion/issues/1395"""
|
|
24
20
|
@property
|
|
25
21
|
def detector_params(self):
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
or
|
|
29
|
-
)
|
|
30
|
-
optional_args = {}
|
|
31
|
-
if self.run_number:
|
|
32
|
-
optional_args["run_number"] = self.run_number
|
|
33
|
-
assert self.detector_distance_mm is not None, (
|
|
34
|
-
"Detector distance must be filled before generating DetectorParams"
|
|
35
|
-
)
|
|
36
|
-
return DetectorParams(
|
|
37
|
-
detector_size_constants=I03Constants.DETECTOR,
|
|
38
|
-
expected_energy_ev=self.demand_energy_ev,
|
|
39
|
-
exposure_time=self.exposure_time_s,
|
|
40
|
-
directory=self.storage_directory,
|
|
41
|
-
prefix=self.file_name,
|
|
42
|
-
detector_distance=self.detector_distance_mm,
|
|
43
|
-
omega_start=self.omega_start_deg or 0,
|
|
44
|
-
omega_increment=0,
|
|
45
|
-
num_images_per_trigger=1,
|
|
46
|
-
num_triggers=self.num_images,
|
|
47
|
-
use_roi_mode=self.use_roi_mode,
|
|
48
|
-
det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
|
|
49
|
-
trigger_mode=self.trigger_mode,
|
|
50
|
-
enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
|
|
51
|
-
or self.features.use_gpu_results,
|
|
52
|
-
**optional_args,
|
|
22
|
+
params = super().detector_params
|
|
23
|
+
params.enable_dev_shm = (
|
|
24
|
+
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
53
25
|
)
|
|
26
|
+
return params
|
|
54
27
|
|
|
55
28
|
|
|
56
29
|
class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGridScan):
|
|
@@ -58,36 +31,14 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
58
31
|
|
|
59
32
|
# These detector params only exist so that we can properly select enable_dev_shm. Remove in
|
|
60
33
|
# https://github.com/DiamondLightSource/hyperion/issues/1395"""
|
|
34
|
+
|
|
61
35
|
@property
|
|
62
36
|
def detector_params(self):
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
or
|
|
66
|
-
)
|
|
67
|
-
optional_args = {}
|
|
68
|
-
if self.run_number:
|
|
69
|
-
optional_args["run_number"] = self.run_number
|
|
70
|
-
assert self.detector_distance_mm is not None, (
|
|
71
|
-
"Detector distance must be filled before generating DetectorParams"
|
|
72
|
-
)
|
|
73
|
-
return DetectorParams(
|
|
74
|
-
detector_size_constants=I03Constants.DETECTOR,
|
|
75
|
-
expected_energy_ev=self.demand_energy_ev,
|
|
76
|
-
exposure_time=self.exposure_time_s,
|
|
77
|
-
directory=self.storage_directory,
|
|
78
|
-
prefix=self.file_name,
|
|
79
|
-
detector_distance=self.detector_distance_mm,
|
|
80
|
-
omega_start=self.omega_start_deg or 0,
|
|
81
|
-
omega_increment=0,
|
|
82
|
-
num_images_per_trigger=1,
|
|
83
|
-
num_triggers=self.num_images,
|
|
84
|
-
use_roi_mode=self.use_roi_mode,
|
|
85
|
-
det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
|
|
86
|
-
trigger_mode=self.trigger_mode,
|
|
87
|
-
enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
|
|
88
|
-
or self.features.use_gpu_results,
|
|
89
|
-
**optional_args,
|
|
37
|
+
params = super().detector_params
|
|
38
|
+
params.enable_dev_shm = (
|
|
39
|
+
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
90
40
|
)
|
|
41
|
+
return params
|
|
91
42
|
|
|
92
43
|
# Relative to common grid scan, stub offsets are defined by config server
|
|
93
44
|
@property
|
|
@@ -75,7 +75,7 @@ class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeature
|
|
|
75
75
|
return DetectorParams(
|
|
76
76
|
detector_size_constants=I03Constants.DETECTOR,
|
|
77
77
|
expected_energy_ev=self.demand_energy_ev,
|
|
78
|
-
|
|
78
|
+
exposure_time_s=self.exposure_time_s,
|
|
79
79
|
directory=self.storage_directory,
|
|
80
80
|
prefix=self.file_name,
|
|
81
81
|
detector_distance=self.detector_distance_mm,
|
|
@@ -192,7 +192,3 @@ class MultiRotationScan(RotationExperiment, SplitScan):
|
|
|
192
192
|
self._num_images_per_scan()[0],
|
|
193
193
|
len(self._num_images_per_scan()),
|
|
194
194
|
)
|
|
195
|
-
|
|
196
|
-
@property
|
|
197
|
-
def ispyb_params(self): # pyright: ignore
|
|
198
|
-
raise ValueError("Please get ispyb params from one of the individual scans")
|
|
@@ -5,14 +5,11 @@ import mx_bluesky.hyperion.experiment_plans as hyperion_plans
|
|
|
5
5
|
from mx_bluesky.common.utils.log import LOGGER
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def setup_context(
|
|
8
|
+
def setup_context() -> BlueskyContext:
|
|
9
9
|
context = BlueskyContext()
|
|
10
10
|
context.with_plan_module(hyperion_plans)
|
|
11
11
|
|
|
12
|
-
context.with_dodal_module(
|
|
13
|
-
get_beamline_based_on_environment_variable(),
|
|
14
|
-
wait_for_connection=wait_for_connection,
|
|
15
|
-
)
|
|
12
|
+
context.with_dodal_module(get_beamline_based_on_environment_variable())
|
|
16
13
|
|
|
17
14
|
LOGGER.info(f"Plans found in context: {context.plan_functions.keys()}")
|
|
18
15
|
|
|
@@ -21,7 +21,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback
|
|
|
21
21
|
RotationNexusFileCallback,
|
|
22
22
|
)
|
|
23
23
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
24
|
-
from mx_bluesky.hyperion.parameters.rotation import
|
|
24
|
+
from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan
|
|
25
25
|
|
|
26
26
|
DISPLAY_CONFIGURATION = "tests/test_data/test_display.configuration"
|
|
27
27
|
ZOOM_LEVELS_XML = "tests/test_data/test_jCameraManZoomLevels.xml"
|
|
@@ -36,33 +36,36 @@ def test_params(filename_stub, dir):
|
|
|
36
36
|
with open(filename) as f:
|
|
37
37
|
return json.loads(f.read())
|
|
38
38
|
|
|
39
|
-
params =
|
|
39
|
+
params = MultiRotationScan(
|
|
40
40
|
**get_params(
|
|
41
|
-
"tests/test_data/parameter_json_files/
|
|
41
|
+
"tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json"
|
|
42
42
|
)
|
|
43
43
|
)
|
|
44
|
+
for scan_params in params.rotation_scans:
|
|
45
|
+
scan_params.x_start_um = 0
|
|
46
|
+
scan_params.y_start_um = 0
|
|
47
|
+
scan_params.z_start_um = 0
|
|
48
|
+
scan_params.scan_width_deg = 360
|
|
44
49
|
params.file_name = filename_stub
|
|
45
|
-
params.scan_width_deg = 360
|
|
46
50
|
params.demand_energy_ev = 12700
|
|
47
51
|
params.storage_directory = str(dir)
|
|
48
|
-
params.x_start_um = 0
|
|
49
|
-
params.y_start_um = 0
|
|
50
|
-
params.z_start_um = 0
|
|
51
52
|
params.exposure_time_s = 0.004
|
|
52
53
|
return params
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
def fake_rotation_scan(
|
|
56
|
-
parameters:
|
|
57
|
+
parameters: MultiRotationScan,
|
|
57
58
|
subscription: RotationNexusFileCallback,
|
|
58
59
|
rotation_devices: RotationScanComposite,
|
|
59
60
|
):
|
|
61
|
+
single_scan_parameters = next(parameters.single_rotation_scans)
|
|
62
|
+
|
|
60
63
|
@bpp.subs_decorator(subscription)
|
|
61
64
|
@bpp.set_run_key_decorator("rotation_scan_with_cleanup_and_subs")
|
|
62
65
|
@bpp.run_decorator( # attach experiment metadata to the start document
|
|
63
66
|
md={
|
|
64
67
|
"subplan_name": CONST.PLAN.ROTATION_OUTER,
|
|
65
|
-
"mx_bluesky_parameters":
|
|
68
|
+
"mx_bluesky_parameters": single_scan_parameters.model_dump_json(),
|
|
66
69
|
"activate_callbacks": "RotationNexusFileCallback",
|
|
67
70
|
}
|
|
68
71
|
)
|
|
@@ -129,7 +132,7 @@ def fake_create_rotation_devices():
|
|
|
129
132
|
|
|
130
133
|
|
|
131
134
|
def sim_rotation_scan_to_create_nexus(
|
|
132
|
-
test_params:
|
|
135
|
+
test_params: MultiRotationScan,
|
|
133
136
|
fake_create_rotation_devices: RotationScanComposite,
|
|
134
137
|
filename_stub,
|
|
135
138
|
RE,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mx-bluesky
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.8
|
|
4
4
|
Summary: Bluesky tools for MX Beamlines at DLS
|
|
5
5
|
Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
|
|
6
6
|
License: Apache License
|
|
@@ -215,7 +215,6 @@ Description-Content-Type: text/x-rst
|
|
|
215
215
|
License-File: LICENSE
|
|
216
216
|
Requires-Dist: annotated_types
|
|
217
217
|
Requires-Dist: caproto
|
|
218
|
-
Requires-Dist: confluent-kafka==2.8.0
|
|
219
218
|
Requires-Dist: fastapi[all]
|
|
220
219
|
Requires-Dist: flask-restful
|
|
221
220
|
Requires-Dist: ispyb
|
|
@@ -237,10 +236,10 @@ Requires-Dist: semver
|
|
|
237
236
|
Requires-Dist: matplotlib
|
|
238
237
|
Requires-Dist: blueapi>=0.5.0
|
|
239
238
|
Requires-Dist: daq-config-server>=0.1.1
|
|
240
|
-
Requires-Dist: ophyd
|
|
241
|
-
Requires-Dist: ophyd-async>=0.
|
|
242
|
-
Requires-Dist: bluesky>=1.13
|
|
243
|
-
Requires-Dist: dls-dodal==1.
|
|
239
|
+
Requires-Dist: ophyd>=1.10.5
|
|
240
|
+
Requires-Dist: ophyd-async>=0.10.0a2
|
|
241
|
+
Requires-Dist: bluesky>=1.13.1
|
|
242
|
+
Requires-Dist: dls-dodal==1.47.0
|
|
244
243
|
Provides-Extra: dev
|
|
245
244
|
Requires-Dist: black; extra == "dev"
|
|
246
245
|
Requires-Dist: build; extra == "dev"
|
|
@@ -254,10 +253,11 @@ Requires-Dist: pipdeptree; extra == "dev"
|
|
|
254
253
|
Requires-Dist: plantweb; extra == "dev"
|
|
255
254
|
Requires-Dist: pre-commit; extra == "dev"
|
|
256
255
|
Requires-Dist: pydata-sphinx-theme>=0.12; extra == "dev"
|
|
257
|
-
Requires-Dist: pyright
|
|
256
|
+
Requires-Dist: pyright; extra == "dev"
|
|
258
257
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
259
258
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
260
259
|
Requires-Dist: pytest-random-order; extra == "dev"
|
|
260
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
261
261
|
Requires-Dist: pytest; extra == "dev"
|
|
262
262
|
Requires-Dist: ruff; extra == "dev"
|
|
263
263
|
Requires-Dist: sphinx-autobuild; extra == "dev"
|
|
@@ -267,6 +267,7 @@ Requires-Dist: tox-direct; extra == "dev"
|
|
|
267
267
|
Requires-Dist: tox; extra == "dev"
|
|
268
268
|
Requires-Dist: types-mock; extra == "dev"
|
|
269
269
|
Requires-Dist: types-requests; extra == "dev"
|
|
270
|
+
Dynamic: license-file
|
|
270
271
|
|
|
271
272
|
mx-bluesky
|
|
272
273
|
===========================
|
|
@@ -285,7 +286,7 @@ Releases https://github.com/DiamondLightSource/mx-bluesky/releases
|
|
|
285
286
|
Getting Started
|
|
286
287
|
===============
|
|
287
288
|
|
|
288
|
-
To get started with developing this repo at DLS run
|
|
289
|
+
To get started with developing this repo at DLS run ``./utility_scripts/dls_dev_env.sh``.
|
|
289
290
|
|
|
290
291
|
If you want to develop interactively at the beamline we recommend using jupyter notebooks. You can get started with this by running::
|
|
291
292
|
|