mx-bluesky 0.3.1__py3-none-any.whl → 1.2.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 (142) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +3 -0
  3. mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
  4. mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
  5. mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
  6. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
  7. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
  8. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
  9. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
  10. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  11. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
  12. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
  13. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
  14. mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
  15. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
  16. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
  17. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
  18. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
  19. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
  20. mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
  21. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  22. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
  23. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
  24. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
  25. mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
  26. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
  27. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
  28. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
  29. mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
  30. mx_bluesky/hyperion/__init__.py +1 -0
  31. mx_bluesky/hyperion/__main__.py +374 -0
  32. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  33. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  34. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  35. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  36. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  37. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  38. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  39. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  40. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  41. mx_bluesky/hyperion/device_setup_plans/utils.py +55 -0
  42. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  43. mx_bluesky/hyperion/exceptions.py +47 -0
  44. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  45. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +93 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +537 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  50. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  51. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  53. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  54. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +237 -0
  55. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +162 -0
  56. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  57. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +60 -0
  58. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +64 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +62 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +86 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  80. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  81. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  85. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  86. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +27 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  88. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  89. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  90. mx_bluesky/hyperion/log.py +99 -0
  91. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  92. mx_bluesky/hyperion/parameters/cli.py +68 -0
  93. mx_bluesky/{parameters → hyperion/parameters}/components.py +80 -26
  94. mx_bluesky/hyperion/parameters/constants.py +158 -0
  95. mx_bluesky/hyperion/parameters/gridscan.py +221 -0
  96. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  97. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  98. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  99. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  100. mx_bluesky/hyperion/tracing.py +28 -0
  101. mx_bluesky/hyperion/utils/context.py +84 -0
  102. mx_bluesky/hyperion/utils/utils.py +25 -0
  103. mx_bluesky/hyperion/utils/validation.py +196 -0
  104. mx_bluesky/jupyter_example.ipynb +3 -2
  105. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +26 -11
  106. mx_bluesky-1.2.0.dist-info/RECORD +140 -0
  107. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +1 -1
  108. mx_bluesky-1.2.0.dist-info/entry_points.txt +8 -0
  109. mx_bluesky/i04/__init__.py +0 -3
  110. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  111. mx_bluesky/parameters/__init__.py +0 -31
  112. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  113. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  114. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  115. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  116. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  119. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  137. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  138. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  139. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  140. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  141. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
  142. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,68 @@
1
+ import argparse
2
+
3
+ from pydantic.dataclasses import dataclass
4
+
5
+ from mx_bluesky._version import version
6
+
7
+
8
+ @dataclass
9
+ class HyperionArgs:
10
+ dev_mode: bool = False
11
+ use_external_callbacks: bool = False
12
+ verbose_event_logging: bool = False
13
+ skip_startup_connection: bool = False
14
+
15
+
16
+ def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
17
+ """adds arguments relevant to hyperion-callbacks."""
18
+ parser.add_argument(
19
+ "--dev",
20
+ action="store_true",
21
+ help="Use dev options, such as local graylog instances and S03",
22
+ )
23
+
24
+
25
+ def parse_callback_dev_mode_arg() -> bool:
26
+ """Returns the bool representing the 'dev_mode' argument."""
27
+ parser = argparse.ArgumentParser()
28
+ _add_callback_relevant_args(parser)
29
+ args = parser.parse_args()
30
+ return args.dev
31
+
32
+
33
+ def parse_cli_args() -> HyperionArgs:
34
+ """Parses all arguments relevant to hyperion. Returns an HyperionArgs dataclass with
35
+ the fields: (verbose_event_logging: bool,
36
+ dev_mode: bool,
37
+ skip_startup_connection: bool,
38
+ external_callbacks: bool)"""
39
+ parser = argparse.ArgumentParser()
40
+ _add_callback_relevant_args(parser)
41
+ parser.add_argument(
42
+ "--verbose-event-logging",
43
+ action="store_true",
44
+ help="Log all bluesky event documents to graylog",
45
+ )
46
+ parser.add_argument(
47
+ "--skip-startup-connection",
48
+ action="store_true",
49
+ help="Skip connecting to EPICS PVs on startup",
50
+ )
51
+ parser.add_argument(
52
+ "--external-callbacks",
53
+ action="store_true",
54
+ help="Run the external hyperion-callbacks service and publish events over ZMQ",
55
+ )
56
+ parser.add_argument(
57
+ "--version",
58
+ help="Print hyperion version string",
59
+ action="version",
60
+ version=version,
61
+ )
62
+ args = parser.parse_args()
63
+ return HyperionArgs(
64
+ verbose_event_logging=args.verbose_event_logging or False,
65
+ dev_mode=args.dev or False,
66
+ skip_startup_connection=args.skip_startup_connection or False,
67
+ use_external_callbacks=args.external_callbacks or False,
68
+ )
@@ -1,20 +1,31 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from abc import abstractmethod
4
5
  from collections.abc import Sequence
5
- from enum import Enum
6
+ from enum import StrEnum
6
7
  from pathlib import Path
7
8
  from typing import SupportsInt, TypeVar
8
9
 
9
- from dodal.devices.aperturescatterguard import AperturePositionGDANames
10
+ from dodal.devices.aperturescatterguard import ApertureValue
10
11
  from dodal.devices.detector import (
11
12
  DetectorParams,
12
13
  TriggerMode,
13
14
  )
14
- from pydantic import BaseModel, Field, root_validator
15
+ from pydantic import (
16
+ BaseModel,
17
+ ConfigDict,
18
+ Field,
19
+ field_serializer,
20
+ field_validator,
21
+ model_validator,
22
+ )
15
23
  from scanspec.core import AxesPoints
16
24
  from semver import Version
17
25
 
26
+ from mx_bluesky.hyperion.external_interaction.config_server import FeatureFlags
27
+ from mx_bluesky.hyperion.parameters.constants import CONST
28
+
18
29
  T = TypeVar("T")
19
30
 
20
31
 
@@ -25,31 +36,24 @@ class ParameterVersion(Version):
25
36
  return version
26
37
  return cls.parse(version)
27
38
 
28
- @classmethod
29
- def __get_validators__(cls):
30
- """Return a list of validator methods for pydantic models."""
31
- yield cls._parse
32
39
 
33
- @classmethod
34
- def __modify_schema__(cls, field_schema):
35
- """Inject/mutate the pydantic field schema in-place."""
36
- field_schema.update(examples=["1.0.2", "2.15.3-alpha", "21.3.15-beta+12345"])
40
+ PARAMETER_VERSION = ParameterVersion.parse("5.1.0")
37
41
 
38
42
 
39
- class RotationAxis(str, Enum):
43
+ class RotationAxis(StrEnum):
40
44
  OMEGA = "omega"
41
45
  PHI = "phi"
42
46
  CHI = "chi"
43
47
  KAPPA = "kappa"
44
48
 
45
49
 
46
- class XyzAxis(str, Enum):
50
+ class XyzAxis(StrEnum):
47
51
  X = "sam_x"
48
52
  Y = "sam_y"
49
53
  Z = "sam_z"
50
54
 
51
55
 
52
- class IspybExperimentType(str, Enum):
56
+ class IspybExperimentType(StrEnum):
53
57
  # Enum values from ispyb column data type
54
58
  SAD = "SAD" # at or slightly above the peak
55
59
  SAD_INVERSE_BEAM = "SAD - Inverse Beam"
@@ -94,36 +98,83 @@ class IspybExperimentType(str, Enum):
94
98
  GRIDSCAN_3D = "Mesh3D"
95
99
 
96
100
 
101
+ class HyperionParameters(BaseModel):
102
+ model_config = ConfigDict(
103
+ arbitrary_types_allowed=True,
104
+ extra="allow",
105
+ )
106
+
107
+ def __hash__(self) -> int:
108
+ return self.json().__hash__()
109
+
110
+ features: FeatureFlags = Field(default=FeatureFlags())
111
+ parameter_model_version: ParameterVersion
112
+
113
+ @field_serializer("parameter_model_version")
114
+ def serialize_parameter_version(self, version: ParameterVersion):
115
+ return str(version)
116
+
117
+ @field_validator("parameter_model_version", mode="before")
118
+ @classmethod
119
+ def _validate_version(cls, version_str: str):
120
+ version = ParameterVersion.parse(version_str)
121
+ assert (
122
+ version >= ParameterVersion(major=PARAMETER_VERSION.major)
123
+ ), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
124
+ assert (
125
+ version <= ParameterVersion(major=PARAMETER_VERSION.major + 1)
126
+ ), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
127
+ return version
128
+
129
+ @classmethod
130
+ def from_json(cls, input: str | None):
131
+ assert input is not None
132
+ return cls(**json.loads(input))
133
+
134
+
97
135
  class WithSnapshot(BaseModel):
98
136
  snapshot_directory: Path
99
- snapshot_omegas_deg: list[float] | None
137
+ snapshot_omegas_deg: list[float] | None = None
100
138
 
101
139
  @property
102
140
  def take_snapshots(self) -> bool:
103
141
  return bool(self.snapshot_omegas_deg)
104
142
 
105
143
 
106
- class DiffractionExperiment(WithSnapshot):
144
+ class WithOptionalEnergyChange(BaseModel):
145
+ demand_energy_ev: float | None = Field(default=None, gt=0)
146
+
147
+
148
+ class WithVisit(BaseModel):
149
+ visit: str = Field(min_length=1)
150
+ zocalo_environment: str = Field(default=CONST.ZOCALO_ENV)
151
+ beamline: str = Field(default=CONST.I03.BEAMLINE, pattern=r"BL\d{2}[BIJS]")
152
+ det_dist_to_beam_converter_path: str = Field(
153
+ default=CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
154
+ )
155
+ insertion_prefix: str = Field(
156
+ default=CONST.I03.INSERTION_PREFIX, pattern=r"SR\d{2}[BIJS]"
157
+ )
158
+ detector_distance_mm: float | None = Field(default=None, gt=0)
159
+
160
+
161
+ class DiffractionExperiment(
162
+ HyperionParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
163
+ ):
107
164
  """For all experiments which use beam"""
108
165
 
109
- visit: str = Field(min_length=5, regex=r"[\w]{2}[\d]+-[\d]+")
110
166
  file_name: str
111
167
  exposure_time_s: float = Field(gt=0)
112
168
  comment: str = Field(default="")
113
- beamline: str = Field(regex=r"BL\d{2}[BIJS]")
114
- insertion_prefix: str = Field(regex=r"SR\d{2}[BIJS]")
115
- det_dist_to_beam_converter_path: str
116
- zocalo_environment: str
117
169
  trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
118
- detector_distance_mm: float | None = Field(default=None, gt=0)
119
- demand_energy_ev: float | None = Field(default=None, gt=0)
120
170
  run_number: int | None = Field(default=None, ge=0)
121
- selected_aperture: AperturePositionGDANames | None = Field(default=None)
171
+ selected_aperture: ApertureValue | None = Field(default=None)
122
172
  transmission_frac: float = Field(default=0.1)
123
173
  ispyb_experiment_type: IspybExperimentType
124
174
  storage_directory: str
125
175
 
126
- @root_validator(pre=True)
176
+ @model_validator(mode="before")
177
+ @classmethod
127
178
  def validate_snapshot_directory(cls, values):
128
179
  snapshot_dir = values.get(
129
180
  "snapshot_directory", Path(values["storage_directory"], "snapshots")
@@ -168,8 +219,11 @@ class WithSample(BaseModel):
168
219
  sample_pin: int | None = None
169
220
 
170
221
 
222
+ class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
223
+
224
+
171
225
  class WithOavCentring(BaseModel):
172
- oav_centring_file: str
226
+ oav_centring_file: str = Field(default=CONST.I03.OAV_CENTRING_FILE)
173
227
 
174
228
 
175
229
  class OptionalXyzStarts(BaseModel):
@@ -0,0 +1,158 @@
1
+ import os
2
+ from enum import Enum
3
+
4
+ from dodal.devices.aperturescatterguard import ApertureValue
5
+ from dodal.devices.detector import EIGER2_X_16M_SIZE
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ TEST_MODE = os.environ.get("HYPERION_TEST_MODE")
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class SimConstants:
13
+ BEAMLINE = "BL03S"
14
+ INSERTION_PREFIX = "SR03S"
15
+ ZOCALO_ENV = "dev_artemis"
16
+ # this one is for unit tests
17
+ ISPYB_CONFIG = "tests/test_data/test_config.cfg"
18
+ # this one is for system tests
19
+ DEV_ISPYB_DATABASE_CFG = "/dls_sw/dasc/mariadb/credentials/ispyb-hyperion-dev.cfg"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class PlanNameConstants:
24
+ # Robot load subplan
25
+ ROBOT_LOAD = "robot_load"
26
+ # Gridscan
27
+ GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
28
+ GRID_DETECT_INNER = "grid_detect"
29
+ GRIDSCAN_OUTER = "run_gridscan_move_and_tidy"
30
+ GRIDSCAN_AND_MOVE = "run_gridscan_and_move"
31
+ GRIDSCAN_MAIN = "run_gridscan"
32
+ DO_FGS = "do_fgs"
33
+ # Rotation scan
34
+ ROTATION_MULTI = "multi_rotation_wrapper"
35
+ ROTATION_OUTER = "rotation_scan_with_cleanup"
36
+ ROTATION_MAIN = "rotation_scan_main"
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class PlanGroupCheckpointConstants:
41
+ # For places to synchronise / stop and wait in plans, use as bluesky group names
42
+ GRID_READY_FOR_DC = "grid_ready_for_data_collection"
43
+ ROTATION_READY_FOR_DC = "rotation_ready_for_data_collection"
44
+ MOVE_GONIO_TO_START = "move_gonio_to_start"
45
+ READY_FOR_OAV = "ready_for_oav"
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class DocDescriptorNames:
50
+ # Robot load event descriptor
51
+ ROBOT_LOAD = "robot_load"
52
+ # For callbacks to use
53
+ OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
54
+ OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
55
+ HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
56
+ HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
57
+ ZOCALO_HW_READ = "zocalo_read_hardware_plan"
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class HardwareConstants:
62
+ OAV_REFRESH_DELAY = 0.3
63
+ PANDA_FGS_RUN_UP_DEFAULT = 0.17
64
+ CRYOJET_MARGIN_MM = 0.2
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class TriggerConstants:
69
+ ZOCALO = "trigger_zocalo_on"
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class GridscanParamConstants:
74
+ WIDTH_UM = 600.0
75
+ EXPOSURE_TIME_S = 0.004
76
+ USE_ROI = True
77
+ BOX_WIDTH_UM = 20.0
78
+ OMEGA_1 = 0.0
79
+ OMEGA_2 = 90.0
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class RotationParamConstants:
84
+ DEFAULT_APERTURE_POSITION = ApertureValue.LARGE
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class DetectorParamConstants:
89
+ BEAM_XY_LUT_PATH = (
90
+ "tests/test_data/test_det_dist_converter.txt"
91
+ if TEST_MODE
92
+ else "/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
93
+ )
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class ExperimentParamConstants:
98
+ DETECTOR = DetectorParamConstants()
99
+ GRIDSCAN = GridscanParamConstants()
100
+ ROTATION = RotationParamConstants()
101
+
102
+
103
+ _test_oav_file = "tests/test_data/test_OAVCentring.json"
104
+ _live_oav_file = "/dls_sw/i03/software/daq_configuration/json/OAVCentring_hyperion.json"
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class I03Constants:
109
+ BASE_DATA_DIR = "/tmp/dls/i03/data/" if TEST_MODE else "/dls/i03/data/"
110
+ BEAMLINE = "BL03S" if TEST_MODE else "BL03I"
111
+ DETECTOR = EIGER2_X_16M_SIZE
112
+ INSERTION_PREFIX = "SR03S" if TEST_MODE else "SR03I"
113
+ OAV_CENTRING_FILE = _test_oav_file if TEST_MODE else _live_oav_file
114
+ SHUTTER_TIME_S = 0.06
115
+ USE_PANDA_FOR_GRIDSCAN = False
116
+ USE_GPU_FOR_GRIDSCAN_ANALYSIS = False
117
+ THAWING_TIME = 20
118
+ USE_CPU_AND_GPU_ZOCALO = False
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class HyperionConstants:
123
+ HARDWARE = HardwareConstants()
124
+ I03 = I03Constants()
125
+ PARAM = ExperimentParamConstants()
126
+ PLAN = PlanNameConstants()
127
+ WAIT = PlanGroupCheckpointConstants()
128
+ SIM = SimConstants()
129
+ TRIGGER = TriggerConstants()
130
+ CALLBACK_0MQ_PROXY_PORTS = (5577, 5578)
131
+ DESCRIPTORS = DocDescriptorNames()
132
+ CONFIG_SERVER_URL = (
133
+ "http://fake-url-not-real"
134
+ if TEST_MODE
135
+ else "https://daq-config.diamond.ac.uk/api"
136
+ )
137
+ GRAYLOG_PORT = 12232
138
+ PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
139
+ ZOCALO_ENV = "dev_artemis" if TEST_MODE else "artemis"
140
+
141
+
142
+ CONST = HyperionConstants()
143
+
144
+
145
+ class Actions(Enum):
146
+ START = "start"
147
+ STOP = "stop"
148
+ SHUTDOWN = "shutdown"
149
+ STATUS = "status"
150
+
151
+
152
+ class Status(Enum):
153
+ WARN = "Warn"
154
+ FAILED = "Failed"
155
+ SUCCESS = "Success"
156
+ BUSY = "Busy"
157
+ ABORTING = "Aborting"
158
+ IDLE = "Idle"
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from dodal.devices.aperturescatterguard import ApertureValue
6
+ from dodal.devices.detector import (
7
+ DetectorParams,
8
+ )
9
+ from dodal.devices.fast_grid_scan import (
10
+ PandAGridScanParams,
11
+ ZebraGridScanParams,
12
+ )
13
+ from pydantic import Field, PrivateAttr
14
+ from scanspec.core import Path as ScanPath
15
+ from scanspec.specs import Line, Static
16
+
17
+ from mx_bluesky.hyperion.parameters.components import (
18
+ DiffractionExperimentWithSample,
19
+ IspybExperimentType,
20
+ OptionalGonioAngleStarts,
21
+ SplitScan,
22
+ WithOavCentring,
23
+ WithOptionalEnergyChange,
24
+ WithScan,
25
+ XyzStarts,
26
+ )
27
+ from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
28
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
29
+
30
+
31
+ class GridCommon(
32
+ DiffractionExperimentWithSample, OptionalGonioAngleStarts, WithOavCentring
33
+ ):
34
+ grid_width_um: float = Field(default=CONST.PARAM.GRIDSCAN.WIDTH_UM)
35
+ exposure_time_s: float = Field(default=CONST.PARAM.GRIDSCAN.EXPOSURE_TIME_S)
36
+ use_roi_mode: bool = Field(default=CONST.PARAM.GRIDSCAN.USE_ROI)
37
+ panda_runup_distance_mm: float = Field(
38
+ default=CONST.HARDWARE.PANDA_FGS_RUN_UP_DEFAULT
39
+ )
40
+ use_panda: bool = Field(default=CONST.I03.USE_PANDA_FOR_GRIDSCAN)
41
+ use_gpu: bool = Field(default=CONST.I03.USE_GPU_FOR_GRIDSCAN_ANALYSIS)
42
+ use_cpu_and_gpu_zocalo: bool = Field(default=CONST.I03.USE_CPU_AND_GPU_ZOCALO)
43
+ ispyb_experiment_type: IspybExperimentType = Field(
44
+ default=IspybExperimentType.GRIDSCAN_3D
45
+ )
46
+ selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL)
47
+
48
+ @property
49
+ def detector_params(self):
50
+ self.det_dist_to_beam_converter_path = (
51
+ self.det_dist_to_beam_converter_path
52
+ or CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
53
+ )
54
+ optional_args = {}
55
+ if self.run_number:
56
+ optional_args["run_number"] = self.run_number
57
+ assert (
58
+ self.detector_distance_mm is not None
59
+ ), "Detector distance must be filled before generating DetectorParams"
60
+ os.makedirs(self.storage_directory, exist_ok=True)
61
+ return DetectorParams(
62
+ detector_size_constants=I03Constants.DETECTOR,
63
+ expected_energy_ev=self.demand_energy_ev,
64
+ exposure_time=self.exposure_time_s,
65
+ directory=self.storage_directory,
66
+ prefix=self.file_name,
67
+ detector_distance=self.detector_distance_mm,
68
+ omega_start=self.omega_start_deg or 0,
69
+ omega_increment=0,
70
+ num_images_per_trigger=1,
71
+ num_triggers=self.num_images,
72
+ use_roi_mode=self.use_roi_mode,
73
+ det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
74
+ trigger_mode=self.trigger_mode,
75
+ enable_dev_shm=self.use_gpu,
76
+ **optional_args,
77
+ )
78
+
79
+
80
+ class GridScanWithEdgeDetect(GridCommon): ...
81
+
82
+
83
+ class PinTipCentreThenXrayCentre(GridCommon):
84
+ tip_offset_um: float = 0
85
+
86
+
87
+ class RobotLoadThenCentre(GridCommon):
88
+ thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
89
+
90
+ def robot_load_params(self):
91
+ my_params = self.model_dump()
92
+ return RobotLoadAndEnergyChange(**my_params)
93
+
94
+ def pin_centre_then_xray_centre_params(self):
95
+ my_params = self.model_dump()
96
+ del my_params["thawing_time"]
97
+ return PinTipCentreThenXrayCentre(**my_params)
98
+
99
+
100
+ class SpecifiedGridScan(GridCommon, XyzStarts, WithScan):
101
+ """A specified grid scan is one which has defined values for the start position,
102
+ grid and box sizes, etc., as opposed to parameters for a plan which will create
103
+ those parameters at some point (e.g. through optical pin detection)."""
104
+
105
+ ...
106
+
107
+
108
+ class ThreeDGridScan(SpecifiedGridScan, SplitScan, WithOptionalEnergyChange):
109
+ """Parameters representing a so-called 3D grid scan, which consists of doing a
110
+ gridscan in X and Y, followed by one in X and Z."""
111
+
112
+ grid1_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_1) # type: ignore
113
+ grid2_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_2)
114
+ x_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
115
+ y_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
116
+ z_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
117
+ y2_start_um: float
118
+ z2_start_um: float
119
+ x_steps: int = Field(gt=0)
120
+ y_steps: int = Field(gt=0)
121
+ z_steps: int = Field(gt=0)
122
+ _set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
123
+
124
+ @property
125
+ def FGS_params(self) -> ZebraGridScanParams:
126
+ return ZebraGridScanParams(
127
+ x_steps=self.x_steps,
128
+ y_steps=self.y_steps,
129
+ z_steps=self.z_steps,
130
+ x_step_size=self.x_step_size_um,
131
+ y_step_size=self.y_step_size_um,
132
+ z_step_size=self.z_step_size_um,
133
+ x_start=self.x_start_um,
134
+ y1_start=self.y_start_um,
135
+ z1_start=self.z_start_um,
136
+ y2_start=self.y2_start_um,
137
+ z2_start=self.z2_start_um,
138
+ set_stub_offsets=self.features.set_stub_offsets,
139
+ dwell_time_ms=self.exposure_time_s * 1000,
140
+ transmission_fraction=self.transmission_frac,
141
+ )
142
+
143
+ @property
144
+ def panda_FGS_params(self) -> PandAGridScanParams:
145
+ if self.y_steps % 2 and self.z_steps > 0:
146
+ raise OddYStepsException(
147
+ "The number of Y steps must be even for a PandA gridscan"
148
+ )
149
+ return PandAGridScanParams(
150
+ x_steps=self.x_steps,
151
+ y_steps=self.y_steps,
152
+ z_steps=self.z_steps,
153
+ x_step_size=self.x_step_size_um,
154
+ y_step_size=self.y_step_size_um,
155
+ z_step_size=self.z_step_size_um,
156
+ x_start=self.x_start_um,
157
+ y1_start=self.y_start_um,
158
+ z1_start=self.z_start_um,
159
+ y2_start=self.y2_start_um,
160
+ z2_start=self.z2_start_um,
161
+ set_stub_offsets=self.features.set_stub_offsets,
162
+ run_up_distance_mm=self.panda_runup_distance_mm,
163
+ transmission_fraction=self.transmission_frac,
164
+ )
165
+
166
+ def do_set_stub_offsets(self, value: bool):
167
+ self._set_stub_offsets = value
168
+
169
+ @property
170
+ def grid_1_spec(self):
171
+ x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
172
+ y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
173
+ grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
174
+ grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
175
+ grid_1_z = Static("sam_z", self.z_start_um)
176
+ return grid_1_y.zip(grid_1_z) * ~grid_1_x
177
+
178
+ @property
179
+ def grid_2_spec(self):
180
+ x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
181
+ z2_end = self.z2_start_um + self.z_step_size_um * (self.z_steps - 1)
182
+ grid_2_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
183
+ grid_2_z = Line("sam_z", self.z2_start_um, z2_end, self.z_steps)
184
+ grid_2_y = Static("sam_y", self.y2_start_um)
185
+ return grid_2_z.zip(grid_2_y) * ~grid_2_x
186
+
187
+ @property
188
+ def scan_indices(self):
189
+ """The first index of each gridscan, useful for writing nexus files/VDS"""
190
+ return [
191
+ 0,
192
+ len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
193
+ ]
194
+
195
+ @property
196
+ def scan_spec(self):
197
+ """A fully specified ScanSpec object representing both grids, with x, y, z and
198
+ omega positions."""
199
+ return self.grid_1_spec.concat(self.grid_2_spec)
200
+
201
+ @property
202
+ def scan_points(self):
203
+ """A list of all the points in the scan_spec."""
204
+ return ScanPath(self.scan_spec.calculate()).consume().midpoints
205
+
206
+ @property
207
+ def scan_points_first_grid(self):
208
+ """A list of all the points in the first grid scan."""
209
+ return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
210
+
211
+ @property
212
+ def scan_points_second_grid(self):
213
+ """A list of all the points in the second grid scan."""
214
+ return ScanPath(self.grid_2_spec.calculate()).consume().midpoints
215
+
216
+ @property
217
+ def num_images(self) -> int:
218
+ return len(self.scan_points["sam_x"])
219
+
220
+
221
+ class OddYStepsException(Exception): ...
@@ -0,0 +1,50 @@
1
+ from typing import TypeVar
2
+
3
+ from pydantic import BaseModel, model_validator
4
+
5
+ from mx_bluesky.hyperion.parameters.components import (
6
+ HyperionParameters,
7
+ WithSample,
8
+ WithVisit,
9
+ )
10
+ from mx_bluesky.hyperion.parameters.gridscan import (
11
+ RobotLoadThenCentre,
12
+ )
13
+ from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan
14
+
15
+ T = TypeVar("T", bound=BaseModel)
16
+
17
+
18
+ def construct_from_values(parent_context: dict, key: str, t: type[T]) -> T:
19
+ values = dict(parent_context)
20
+ values |= values[key]
21
+ return t(**values)
22
+
23
+
24
+ class LoadCentreCollect(HyperionParameters, WithVisit, WithSample):
25
+ """Experiment parameters to perform the combined robot load,
26
+ pin-tip centre and rotation scan operations."""
27
+
28
+ robot_load_then_centre: RobotLoadThenCentre
29
+ multi_rotation_scan: MultiRotationScan
30
+
31
+ @model_validator(mode="before")
32
+ @classmethod
33
+ def validate_model(cls, values):
34
+ allowed_keys = (
35
+ LoadCentreCollect.model_fields.keys()
36
+ | RobotLoadThenCentre.model_fields.keys()
37
+ | MultiRotationScan.model_fields.keys()
38
+ )
39
+ disallowed_keys = values.keys() - allowed_keys
40
+ assert (
41
+ disallowed_keys == set()
42
+ ), f"Unexpected fields found in LoadCentreCollect {disallowed_keys}"
43
+
44
+ values["robot_load_then_centre"] = construct_from_values(
45
+ values, "robot_load_then_centre", RobotLoadThenCentre
46
+ )
47
+ values["multi_rotation_scan"] = construct_from_values(
48
+ values, "multi_rotation_scan", MultiRotationScan
49
+ )
50
+ return values
@@ -0,0 +1,16 @@
1
+ from pydantic import Field
2
+
3
+ from mx_bluesky.hyperion.parameters.components import (
4
+ HyperionParameters,
5
+ WithOptionalEnergyChange,
6
+ WithSample,
7
+ WithSnapshot,
8
+ WithVisit,
9
+ )
10
+ from mx_bluesky.hyperion.parameters.constants import CONST
11
+
12
+
13
+ class RobotLoadAndEnergyChange(
14
+ HyperionParameters, WithSample, WithSnapshot, WithOptionalEnergyChange, WithVisit
15
+ ):
16
+ thawing_time: float = Field(default=CONST.I03.THAWING_TIME)