mx-bluesky 1.1.0__py3-none-any.whl → 1.4.0__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 (81) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/thawing_plan.py +48 -10
  6. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  7. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +68 -90
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +104 -126
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +139 -162
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +25 -36
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +24 -34
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +14 -11
  14. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  15. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  16. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  17. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +31 -7
  18. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  19. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +1 -1
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +8 -18
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +2 -2
  23. mx_bluesky/common/__init__.py +0 -0
  24. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  25. mx_bluesky/common/parameters/components.py +221 -0
  26. mx_bluesky/common/parameters/constants.py +133 -0
  27. mx_bluesky/common/plans/__init__.py +1 -0
  28. mx_bluesky/common/plans/do_fgs.py +121 -0
  29. mx_bluesky/common/utils/log.py +116 -0
  30. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  31. mx_bluesky/hyperion/__main__.py +11 -9
  32. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
  33. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  34. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  35. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -2
  36. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +48 -17
  37. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  38. mx_bluesky/hyperion/device_setup_plans/utils.py +13 -2
  39. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  40. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -0
  41. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +59 -108
  42. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +7 -5
  43. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  44. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +19 -18
  45. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -5
  46. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  47. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +17 -17
  48. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +241 -0
  49. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +24 -181
  50. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +6 -4
  51. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +3 -11
  52. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
  53. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +18 -0
  54. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -9
  55. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +18 -13
  56. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +32 -15
  57. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  58. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +3 -5
  59. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +4 -3
  60. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +23 -18
  61. mx_bluesky/hyperion/external_interaction/config_server.py +22 -10
  62. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  63. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +0 -2
  64. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  65. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  66. mx_bluesky/hyperion/log.py +0 -84
  67. mx_bluesky/hyperion/parameters/components.py +1 -242
  68. mx_bluesky/hyperion/parameters/constants.py +22 -118
  69. mx_bluesky/hyperion/parameters/gridscan.py +20 -11
  70. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  71. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  72. mx_bluesky/hyperion/parameters/rotation.py +9 -5
  73. mx_bluesky/hyperion/utils/utils.py +17 -0
  74. mx_bluesky/hyperion/utils/validation.py +5 -6
  75. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/METADATA +4 -2
  76. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/RECORD +80 -70
  77. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/WHEEL +1 -1
  78. mx_bluesky/example.py +0 -19
  79. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/LICENSE +0 -0
  80. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/entry_points.txt +0 -0
  81. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,17 @@
1
- import logging
1
+ import argparse
2
2
  import subprocess
3
3
  from os import environ
4
4
  from pathlib import Path
5
5
 
6
- logger = logging.getLogger("I24ssx.run")
6
+ from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
7
+ from mx_bluesky.beamlines.i24.serial.parameters import SSXType
8
+
9
+
10
+ def _parse_input(expt: SSXType):
11
+ parser = argparse.ArgumentParser(description=f"Run a {expt} collection.")
12
+ parser.add_argument("-t", "--test", action="store_true", help="Run in test mode.")
13
+ args = parser.parse_args()
14
+ return args
7
15
 
8
16
 
9
17
  def get_location(default: str = "dev") -> str:
@@ -19,18 +27,26 @@ def _get_file_path() -> Path:
19
27
 
20
28
 
21
29
  def run_extruder():
30
+ args = _parse_input(SSXType.EXTRUDER)
22
31
  loc = get_location()
23
- logger.debug(f"Running on {loc}.")
32
+ SSX_LOGGER.info(f"Running on {loc}.")
24
33
  edm_path = get_edm_path()
25
34
  filepath = _get_file_path()
26
- logger.debug(f"Running {filepath}/run_extruder.sh")
27
- subprocess.run(["sh", filepath / "run_extruder.sh", edm_path.as_posix()])
35
+ test_mode = "--test" if args.test else ""
36
+ SSX_LOGGER.debug(f"Running {filepath}/run_extruder.sh")
37
+ subprocess.run(
38
+ ["bash", filepath / "run_extruder.sh", edm_path.as_posix(), test_mode]
39
+ )
28
40
 
29
41
 
30
42
  def run_fixed_target():
43
+ args = _parse_input(SSXType.FIXED)
31
44
  loc = get_location()
32
- logger.info(f"Running on {loc}.")
45
+ SSX_LOGGER.info(f"Running on {loc}.")
33
46
  edm_path = get_edm_path()
34
47
  filepath = _get_file_path()
35
- logger.debug(f"Running {filepath}/run_fixed_target.sh")
36
- subprocess.run(["sh", filepath / "run_fixed_target.sh", edm_path.as_posix()])
48
+ test_mode = "--test" if args.test else ""
49
+ SSX_LOGGER.debug(f"Running {filepath}/run_fixed_target.sh")
50
+ subprocess.run(
51
+ ["bash", filepath / "run_fixed_target.sh", edm_path.as_posix(), test_mode]
52
+ )
@@ -22,8 +22,6 @@ def caget(pv):
22
22
  a = Popen(["caget", pv], stdout=PIPE, stderr=PIPE)
23
23
  a_stdout, a_stderr = a.communicate()
24
24
  val = a_stdout.split()[1].decode("ascii")
25
- # val = evaluate(val)
26
- # val = val.decode('ascii')
27
25
  except Exception:
28
26
  print("Exception in ca_py3.py caget, maybe this PV doesnt exist:", pv)
29
27
  pass
@@ -42,7 +42,7 @@ def move_detector_stage_to_position_plan(
42
42
  logger.debug(
43
43
  f"Waiting for detector move. Detector distance: {detector_distance} mm."
44
44
  )
45
- yield from bps.mv(detector_stage.z, detector_distance)
45
+ yield from bps.mv(detector_stage.z, detector_distance) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
46
46
 
47
47
 
48
48
  def modechange(action):
@@ -2,8 +2,6 @@
2
2
  Utilities for defining the detector in use, and moving the stage.
3
3
  """
4
4
 
5
- import logging
6
- import time
7
5
  from collections.abc import Generator
8
6
  from enum import IntEnum
9
7
 
@@ -13,7 +11,7 @@ from bluesky.utils import Msg
13
11
  from dodal.common import inject
14
12
  from dodal.devices.i24.i24_detector_motion import DetectorMotion
15
13
 
16
- from mx_bluesky.beamlines.i24.serial import log
14
+ from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
17
15
  from mx_bluesky.beamlines.i24.serial.parameters import SSXType
18
16
  from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
19
17
  from mx_bluesky.beamlines.i24.serial.setup_beamline.ca import caget
@@ -23,8 +21,6 @@ from mx_bluesky.beamlines.i24.serial.setup_beamline.pv_abstract import (
23
21
  Pilatus,
24
22
  )
25
23
 
26
- logger = logging.getLogger("I24ssx.sup_det")
27
-
28
24
  EXPT_TYPE_DETECTOR_PVS = {
29
25
  SSXType.FIXED: pv.me14e_gp101,
30
26
  SSXType.EXTRUDER: pv.ioc12_gp15,
@@ -39,11 +35,6 @@ class DetRequest(IntEnum):
39
35
  return self.name
40
36
 
41
37
 
42
- def setup_logging():
43
- logfile = time.strftime("SSXdetectorOps_%d%B%y.log").lower()
44
- log.config(logfile)
45
-
46
-
47
38
  class UnknownDetectorType(Exception):
48
39
  pass
49
40
 
@@ -53,19 +44,19 @@ def get_detector_type(detector_stage: DetectorMotion) -> Generator[Msg, None, De
53
44
  # DetectorMotion should also be used for this.
54
45
  # This should be part of https://github.com/DiamondLightSource/mx_bluesky/issues/51
55
46
  if float(det_y) < Eiger.det_y_threshold:
56
- logger.info("Eiger detector in use.")
47
+ SSX_LOGGER.info("Eiger detector in use.")
57
48
  return Eiger()
58
49
  elif float(det_y) > Pilatus.det_y_threshold:
59
- logger.info("Pilatus detector in use.")
50
+ SSX_LOGGER.info("Pilatus detector in use.")
60
51
  return Pilatus()
61
52
  else:
62
- logger.error("Detector not found.")
53
+ SSX_LOGGER.error("Detector not found.")
63
54
  raise UnknownDetectorType("Detector not found.")
64
55
 
65
56
 
66
57
  def _move_detector_stage(detector_stage: DetectorMotion, target: float) -> MsgGenerator:
67
- logger.info(f"Moving detector stage to target position: {target}.")
68
- yield from bps.mv(detector_stage.y, target)
58
+ SSX_LOGGER.info(f"Moving detector stage to target position: {target}.")
59
+ yield from bps.mv(detector_stage.y, target) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
69
60
 
70
61
 
71
62
  # Workaround in case the PV value has been set to the detector name
@@ -94,14 +85,13 @@ def _get_requested_detector(det_type_pv: str) -> str:
94
85
  def setup_detector_stage(
95
86
  expt_type: SSXType, detector_stage: DetectorMotion = inject("detector_motion")
96
87
  ) -> MsgGenerator:
97
- setup_logging()
98
88
  # Grab the correct PV depending on experiment
99
89
  # Its value is set with MUX on edm screen
100
90
  det_type_pv = EXPT_TYPE_DETECTOR_PVS[expt_type]
101
91
  requested_detector = _get_requested_detector(det_type_pv)
102
- logger.info(f"Requested detector: {requested_detector}.")
92
+ SSX_LOGGER.info(f"Requested detector: {requested_detector}.")
103
93
  det_y_target = (
104
94
  Eiger.det_y_target if requested_detector == "eiger" else Pilatus.det_y_target
105
95
  )
106
96
  yield from _move_detector_stage(detector_stage, det_y_target)
107
- logger.info("Detector setup done.")
97
+ SSX_LOGGER.info("Detector setup done.")
@@ -70,12 +70,12 @@ def get_zebra_settings_for_extruder(
70
70
 
71
71
 
72
72
  def arm_zebra(zebra: Zebra):
73
- yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
73
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
74
74
  logger.info("Zebra armed.")
75
75
 
76
76
 
77
77
  def disarm_zebra(zebra: Zebra):
78
- yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
78
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
79
79
  logger.info("Zebra disarmed.")
80
80
 
81
81
 
File without changes
@@ -0,0 +1,14 @@
1
+ import bluesky.plan_stubs as bps
2
+ from dodal.devices.eiger import EigerDetector
3
+
4
+ from mx_bluesky.common.parameters.constants import DocDescriptorNames
5
+
6
+
7
+ def read_hardware_for_zocalo(detector: EigerDetector):
8
+ """ "
9
+ If the RunEngine is subscribed to the ZocaloCallback, this plan will also trigger zocalo.
10
+ A bluesky run must be open to use this plan
11
+ """
12
+ yield from bps.create(name=DocDescriptorNames.ZOCALO_HW_READ)
13
+ yield from bps.read(detector.odin.file_writer.id) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
14
+ yield from bps.save()
@@ -0,0 +1,221 @@
1
+ import json
2
+ from abc import abstractmethod
3
+ from collections.abc import Sequence
4
+ from enum import StrEnum
5
+ from pathlib import Path
6
+ from typing import SupportsInt
7
+
8
+ from dodal.devices.aperturescatterguard import ApertureValue
9
+ from dodal.devices.detector import (
10
+ DetectorParams,
11
+ TriggerMode,
12
+ )
13
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
14
+ from pydantic_extra_types.semantic_version import SemanticVersion
15
+ from scanspec.core import AxesPoints
16
+ from semver import Version
17
+
18
+ from mx_bluesky.common.parameters.constants import TEST_MODE, DetectorParamConstants
19
+
20
+ PARAMETER_VERSION = Version.parse("5.2.0")
21
+
22
+
23
+ class RotationAxis(StrEnum):
24
+ OMEGA = "omega"
25
+ PHI = "phi"
26
+ CHI = "chi"
27
+ KAPPA = "kappa"
28
+
29
+
30
+ class XyzAxis(StrEnum):
31
+ X = "sam_x"
32
+ Y = "sam_y"
33
+ Z = "sam_z"
34
+
35
+
36
+ class IspybExperimentType(StrEnum):
37
+ # Enum values from ispyb column data type
38
+ SAD = "SAD" # at or slightly above the peak
39
+ SAD_INVERSE_BEAM = "SAD - Inverse Beam"
40
+ OSC = "OSC" # "native" (in the absence of a heavy atom)
41
+ COLLECT_MULTIWEDGE = (
42
+ "Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy???
43
+ )
44
+ MAD = "MAD"
45
+ HELICAL = "Helical"
46
+ MULTI_POSITIONAL = "Multi-positional"
47
+ MESH = "Mesh"
48
+ BURN = "Burn"
49
+ MAD_INVERSE_BEAM = "MAD - Inverse Beam"
50
+ CHARACTERIZATION = "Characterization"
51
+ DEHYDRATION = "Dehydration"
52
+ TOMO = "tomo"
53
+ EXPERIMENT = "experiment"
54
+ EM = "EM"
55
+ PDF = "PDF"
56
+ PDF_BRAGG = "PDF+Bragg"
57
+ BRAGG = "Bragg"
58
+ SINGLE_PARTICLE = "single particle"
59
+ SERIAL_FIXED = "Serial Fixed"
60
+ SERIAL_JET = "Serial Jet"
61
+ STANDARD = "Standard" # Routine structure determination experiment
62
+ TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time
63
+ DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell
64
+ CUSTOM = "Custom" # Special or non-standard data collection
65
+ XRF_MAP = "XRF map"
66
+ ENERGY_SCAN = "Energy scan"
67
+ XRF_SPECTRUM = "XRF spectrum"
68
+ XRF_MAP_XAS = "XRF map xas"
69
+ MESH_3D = "Mesh3D"
70
+ SCREENING = "Screening"
71
+ STILL = "Still"
72
+ SSX_CHIP = "SSX-Chip"
73
+ SSX_JET = "SSX-Jet"
74
+
75
+ # Aliases for historic hyperion experiment type mapping
76
+ ROTATION = "SAD"
77
+ GRIDSCAN_2D = "mesh"
78
+ GRIDSCAN_3D = "Mesh3D"
79
+
80
+
81
+ class MxBlueskyParameters(BaseModel):
82
+ model_config = ConfigDict(
83
+ arbitrary_types_allowed=True,
84
+ extra="allow",
85
+ )
86
+
87
+ def __hash__(self) -> int:
88
+ return self.model_dump_json().__hash__()
89
+
90
+ parameter_model_version: SemanticVersion
91
+
92
+ @field_validator("parameter_model_version")
93
+ @classmethod
94
+ def _validate_version(cls, version: Version):
95
+ assert (
96
+ version >= Version(major=PARAMETER_VERSION.major)
97
+ ), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
98
+ assert (
99
+ version <= Version(major=PARAMETER_VERSION.major + 1)
100
+ ), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
101
+ return version
102
+
103
+ @classmethod
104
+ def from_json(cls, input: str | None):
105
+ assert input is not None
106
+ return cls(**json.loads(input))
107
+
108
+
109
+ class WithSnapshot(BaseModel):
110
+ snapshot_directory: Path
111
+ snapshot_omegas_deg: list[float] | None = None
112
+
113
+ @property
114
+ def take_snapshots(self) -> bool:
115
+ return bool(self.snapshot_omegas_deg)
116
+
117
+
118
+ class WithOptionalEnergyChange(BaseModel):
119
+ demand_energy_ev: float | None = Field(default=None, gt=0)
120
+
121
+
122
+ class WithVisit(BaseModel):
123
+ beamline: str = Field(default="BL03I", pattern=r"BL\d{2}[BIJS]")
124
+ visit: str = Field(min_length=1)
125
+ det_dist_to_beam_converter_path: str = Field(
126
+ default=DetectorParamConstants.BEAM_XY_LUT_PATH
127
+ )
128
+ insertion_prefix: str = "SR03S" if TEST_MODE else "SR03I"
129
+ detector_distance_mm: float | None = Field(default=None, gt=0)
130
+
131
+
132
+ class DiffractionExperiment(
133
+ MxBlueskyParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
134
+ ):
135
+ """For all experiments which use beam"""
136
+
137
+ file_name: str
138
+ exposure_time_s: float = Field(gt=0)
139
+ comment: str = Field(default="")
140
+ trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
141
+ run_number: int | None = Field(default=None, ge=0)
142
+ selected_aperture: ApertureValue | None = Field(default=None)
143
+ transmission_frac: float = Field(default=0.1)
144
+ ispyb_experiment_type: IspybExperimentType
145
+ storage_directory: str
146
+
147
+ @model_validator(mode="before")
148
+ @classmethod
149
+ def validate_snapshot_directory(cls, values):
150
+ snapshot_dir = values.get(
151
+ "snapshot_directory", Path(values["storage_directory"], "snapshots")
152
+ )
153
+ values["snapshot_directory"] = (
154
+ snapshot_dir if isinstance(snapshot_dir, Path) else Path(snapshot_dir)
155
+ )
156
+ return values
157
+
158
+ @property
159
+ def num_images(self) -> int:
160
+ return 0
161
+
162
+ @property
163
+ @abstractmethod
164
+ def detector_params(self) -> DetectorParams: ...
165
+
166
+
167
+ class WithScan(BaseModel):
168
+ """For experiments where the scan is known"""
169
+
170
+ @property
171
+ @abstractmethod
172
+ def scan_points(self) -> AxesPoints: ...
173
+
174
+ @property
175
+ @abstractmethod
176
+ def num_images(self) -> int: ...
177
+
178
+
179
+ class SplitScan(BaseModel):
180
+ @property
181
+ @abstractmethod
182
+ def scan_indices(self) -> Sequence[SupportsInt]:
183
+ """Should return the first index of each scan (i.e. for each nexus file)"""
184
+ ...
185
+
186
+
187
+ class WithSample(BaseModel):
188
+ sample_id: int
189
+ sample_puck: int | None = None
190
+ sample_pin: int | None = None
191
+
192
+
193
+ class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
194
+
195
+
196
+ class OptionalXyzStarts(BaseModel):
197
+ x_start_um: float | None = None
198
+ y_start_um: float | None = None
199
+ z_start_um: float | None = None
200
+
201
+
202
+ class XyzStarts(BaseModel):
203
+ x_start_um: float
204
+ y_start_um: float
205
+ z_start_um: float
206
+
207
+ def _start_for_axis(self, axis: XyzAxis) -> float:
208
+ match axis:
209
+ case XyzAxis.X:
210
+ return self.x_start_um
211
+ case XyzAxis.Y:
212
+ return self.y_start_um
213
+ case XyzAxis.Z:
214
+ return self.z_start_um
215
+
216
+
217
+ class OptionalGonioAngleStarts(BaseModel):
218
+ omega_start_deg: float | None = None
219
+ phi_start_deg: float | None = None
220
+ chi_start_deg: float | None = None
221
+ kappa_start_deg: float | None = None
@@ -0,0 +1,133 @@
1
+ from enum import Enum
2
+
3
+ from dodal.devices.aperturescatterguard import ApertureValue
4
+ from dodal.devices.zocalo.zocalo_constants import ZOCALO_ENV as ZOCALO_ENV_FROM_DODAL
5
+ from dodal.utils import get_beamline_name
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ BEAMLINE = get_beamline_name("test")
9
+ TEST_MODE = BEAMLINE == "test"
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class DocDescriptorNames:
14
+ # Robot load event descriptor
15
+ ROBOT_LOAD = "robot_load"
16
+ # For callbacks to use
17
+ OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
18
+ OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
19
+ HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
20
+ HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
21
+ ZOCALO_HW_READ = "zocalo_read_hardware_plan"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class OavConstants:
26
+ OAV_CONFIG_JSON = (
27
+ "tests/test_data/test_OAVCentring.json"
28
+ if TEST_MODE
29
+ else (
30
+ f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
31
+ )
32
+ )
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class PlanNameConstants:
37
+ # Robot load subplan
38
+ ROBOT_LOAD = "robot_load"
39
+ # Gridscan
40
+ GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
41
+ GRID_DETECT_INNER = "grid_detect"
42
+ GRIDSCAN_OUTER = "run_gridscan_move_and_tidy"
43
+ GRIDSCAN_AND_MOVE = "run_gridscan_and_move"
44
+ GRIDSCAN_MAIN = "run_gridscan"
45
+ DO_FGS = "do_fgs"
46
+ # Rotation scan
47
+ ROTATION_MULTI = "multi_rotation_wrapper"
48
+ ROTATION_OUTER = "rotation_scan_with_cleanup"
49
+ ROTATION_MAIN = "rotation_scan_main"
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class EnvironmentConstants:
54
+ ZOCALO_ENV = ZOCALO_ENV_FROM_DODAL
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class TriggerConstants:
59
+ ZOCALO = "trigger_zocalo_on"
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class HardwareConstants:
64
+ OAV_REFRESH_DELAY = 0.3
65
+ PANDA_FGS_RUN_UP_DEFAULT = 0.17
66
+ CRYOJET_MARGIN_MM = 0.2
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class GridscanParamConstants:
71
+ WIDTH_UM = 600.0
72
+ EXPOSURE_TIME_S = 0.004
73
+ USE_ROI = True
74
+ BOX_WIDTH_UM = 20.0
75
+ OMEGA_1 = 0.0
76
+ OMEGA_2 = 90.0
77
+ PANDA_RUN_UP_DISTANCE_MM = 0.2
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class RotationParamConstants:
82
+ DEFAULT_APERTURE_POSITION = ApertureValue.LARGE
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class DetectorParamConstants:
87
+ BEAM_XY_LUT_PATH = (
88
+ "tests/test_data/test_det_dist_converter.txt"
89
+ if TEST_MODE
90
+ else "/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
91
+ )
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class ExperimentParamConstants:
96
+ DETECTOR = DetectorParamConstants()
97
+ GRIDSCAN = GridscanParamConstants()
98
+ ROTATION = RotationParamConstants()
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class PlanGroupCheckpointConstants:
103
+ # For places to synchronise / stop and wait in plans, use as bluesky group names
104
+ GRID_READY_FOR_DC = "grid_ready_for_data_collection"
105
+ ROTATION_READY_FOR_DC = "rotation_ready_for_data_collection"
106
+ MOVE_GONIO_TO_START = "move_gonio_to_start"
107
+ READY_FOR_OAV = "ready_for_oav"
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class SimConstants:
112
+ BEAMLINE = "BL03S"
113
+ INSERTION_PREFIX = "SR03S"
114
+ # this one is for unit tests
115
+ ISPYB_CONFIG = "tests/test_data/test_config.cfg"
116
+ # this one is for system tests
117
+ DEV_ISPYB_DATABASE_CFG = "/dls_sw/dasc/mariadb/credentials/ispyb-hyperion-dev.cfg"
118
+
119
+
120
+ class Actions(Enum):
121
+ START = "start"
122
+ STOP = "stop"
123
+ SHUTDOWN = "shutdown"
124
+ STATUS = "status"
125
+
126
+
127
+ class Status(Enum):
128
+ WARN = "Warn"
129
+ FAILED = "Failed"
130
+ SUCCESS = "Success"
131
+ BUSY = "Busy"
132
+ ABORTING = "Aborting"
133
+ IDLE = "Idle"
@@ -0,0 +1 @@
1
+ """Common MX plans which includes open and close data collection runs. These be used in isolation or as part of a larger plan"""
@@ -0,0 +1,121 @@
1
+ from collections.abc import Callable
2
+ from time import time
3
+
4
+ import bluesky.plan_stubs as bps
5
+ import bluesky.preprocessors as bpp
6
+ from blueapi.core import MsgGenerator
7
+ from dodal.devices.eiger import EigerDetector
8
+ from dodal.devices.fast_grid_scan import FastGridScanCommon
9
+ from dodal.devices.synchrotron import Synchrotron
10
+ from dodal.devices.zocalo.zocalo_results import (
11
+ ZOCALO_STAGE_GROUP,
12
+ )
13
+ from dodal.log import LOGGER
14
+ from dodal.plans.check_topup import check_topup_and_wait_if_necessary
15
+ from scanspec.core import AxesPoints, Axis
16
+
17
+ from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
18
+ read_hardware_for_zocalo,
19
+ )
20
+ from mx_bluesky.common.parameters.constants import (
21
+ EnvironmentConstants,
22
+ PlanNameConstants,
23
+ TriggerConstants,
24
+ )
25
+ from mx_bluesky.common.utils.tracing import TRACER
26
+
27
+
28
+ def _wait_for_zocalo_to_stage_then_do_fgs(
29
+ grid_scan_device: FastGridScanCommon,
30
+ detector: EigerDetector,
31
+ synchrotron: Synchrotron,
32
+ during_collection_plan: Callable[[], MsgGenerator] | None = None,
33
+ ):
34
+ expected_images = yield from bps.rd(grid_scan_device.expected_images)
35
+ exposure_sec_per_image = yield from bps.rd(detector.cam.acquire_time) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
36
+ LOGGER.info("waiting for topup if necessary...")
37
+ yield from check_topup_and_wait_if_necessary(
38
+ synchrotron,
39
+ expected_images * exposure_sec_per_image,
40
+ 30.0,
41
+ )
42
+
43
+ # Make sure ZocaloResults queue is clear and ready to accept our new data. Zocalo MUST
44
+ # have been staged using ZOCALO_STAGE_GROUP prior to this
45
+ LOGGER.info("Waiting for Zocalo device queue to have been cleared...")
46
+ yield from bps.wait(ZOCALO_STAGE_GROUP)
47
+
48
+ # Triggers Zocalo if RE is subscribed to ZocaloCallback
49
+ yield from read_hardware_for_zocalo(detector)
50
+ LOGGER.info("Wait for all moves with no assigned group")
51
+ yield from bps.wait()
52
+
53
+ LOGGER.info("kicking off FGS")
54
+ yield from bps.kickoff(grid_scan_device, wait=True)
55
+ gridscan_start_time = time()
56
+ if during_collection_plan:
57
+ yield from during_collection_plan()
58
+ LOGGER.info("completing FGS")
59
+ yield from bps.complete(grid_scan_device, wait=True)
60
+ # Remove this logging statement once metrics have been added
61
+ LOGGER.info(
62
+ f"Grid scan motion program took {round(time()-gridscan_start_time,2)} to complete"
63
+ )
64
+
65
+
66
+ def kickoff_and_complete_gridscan(
67
+ gridscan: FastGridScanCommon,
68
+ detector: EigerDetector, # Once Eiger inherits from StandardDetector, use that type instead
69
+ synchrotron: Synchrotron,
70
+ scan_points: list[AxesPoints[Axis]],
71
+ scan_start_indices: list[int],
72
+ plan_during_collection: Callable[[], MsgGenerator] | None = None,
73
+ zocalo_environment: str = EnvironmentConstants.ZOCALO_ENV,
74
+ ):
75
+ """Triggers a grid scan motion program and waits for completion, accounting for synchrotron topup.
76
+ If the RunEngine is subscribed to ZocaloCallback, this plan will also trigger Zocalo.
77
+
78
+ Can be used for multiple successive grid scans, see Hyperion's usage
79
+
80
+ Args:
81
+ gridscan (FastGridScanCommon): Device which can trigger a fast grid scan and wait for completion
82
+ detector (EigerDetector) Detector device
83
+ synchrotron (Synchrotron): Synchrotron device
84
+ scan_points (list[AxesPoints[Axis]]): Each element in the list contains all the grid points for that grid scan.
85
+ Two elements in this list indicates that two grid scans will be done, eg for Hyperion's 3D grid scans.
86
+ scan_start_indices (list[int]): Contains the first index of each grid scan
87
+ plan_during_collection (Optional, MsgGenerator): Generic plan called in between kickoff and completion,
88
+ eg waiting on zocalo.
89
+ zocalo_environment (Optional, str) Used for zocalo connection
90
+ """
91
+
92
+ assert len(scan_points) == len(
93
+ scan_start_indices
94
+ ), "scan_points and scan_start_indices must be lists of the same length!"
95
+
96
+ plan_name = PlanNameConstants.DO_FGS
97
+
98
+ @TRACER.start_as_current_span(plan_name)
99
+ @bpp.set_run_key_decorator(plan_name)
100
+ @bpp.run_decorator(
101
+ md={
102
+ "subplan_name": plan_name,
103
+ TriggerConstants.ZOCALO: plan_name,
104
+ "scan_points": scan_points,
105
+ "scan_start_indices": scan_start_indices,
106
+ "zocalo_environment": zocalo_environment,
107
+ }
108
+ )
109
+ @bpp.contingency_decorator(
110
+ except_plan=lambda e: (yield from bps.stop(detector)), # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
111
+ else_plan=lambda: (yield from bps.unstage(detector)), # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
112
+ )
113
+ def _decorated_do_fgs():
114
+ yield from _wait_for_zocalo_to_stage_then_do_fgs(
115
+ gridscan,
116
+ detector,
117
+ synchrotron,
118
+ during_collection_plan=plan_during_collection,
119
+ )
120
+
121
+ yield from _decorated_do_fgs()