mx-bluesky 0.0.2__py3-none-any.whl → 1.1.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 (150) hide show
  1. mx_bluesky/__main__.py +1 -2
  2. mx_bluesky/_version.py +14 -2
  3. mx_bluesky/beamlines/i04/__init__.py +3 -0
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +45 -0
  5. mx_bluesky/beamlines/i04/thawing_plan.py +85 -0
  6. mx_bluesky/beamlines/i24/serial/__init__.py +49 -0
  7. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +12 -0
  8. mx_bluesky/{I24 → beamlines/i24}/serial/dcid.py +53 -41
  9. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -4
  10. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +28 -32
  11. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -1
  12. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +516 -0
  13. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -4
  14. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -4
  15. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +273 -223
  16. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -1
  17. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +12 -13
  18. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -1
  19. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -1
  20. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -1
  21. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -1
  22. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +273 -143
  23. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  24. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  25. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/ft_utils.py +24 -1
  26. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +808 -0
  27. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +377 -416
  28. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +34 -40
  29. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +328 -0
  30. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +66 -48
  31. mx_bluesky/{I24 → beamlines/i24}/serial/log.py +66 -19
  32. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  33. mx_bluesky/beamlines/i24/serial/parameters/constants.py +47 -0
  34. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +103 -0
  35. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +9 -0
  36. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +1 -1
  37. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +1 -1
  38. mx_bluesky/beamlines/i24/serial/parameters/utils.py +42 -0
  39. mx_bluesky/beamlines/i24/serial/run_extruder.sh +19 -0
  40. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +22 -0
  41. mx_bluesky/beamlines/i24/serial/run_serial.py +36 -0
  42. mx_bluesky/{I24 → beamlines/i24}/serial/set_visit_directory.sh +6 -1
  43. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv.py +1 -62
  44. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +6 -7
  45. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +90 -269
  46. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +47 -40
  47. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +459 -0
  48. mx_bluesky/beamlines/i24/serial/start_blueapi.sh +28 -0
  49. mx_bluesky/beamlines/i24/serial/write_nexus.py +105 -0
  50. mx_bluesky/example.py +4 -4
  51. mx_bluesky/hyperion/__init__.py +1 -0
  52. mx_bluesky/hyperion/__main__.py +374 -0
  53. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  54. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  55. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  56. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  57. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  58. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  59. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  60. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  61. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  62. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  63. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  64. mx_bluesky/hyperion/exceptions.py +47 -0
  65. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  66. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  67. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  68. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  69. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  70. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  71. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  72. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  73. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  74. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  75. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  76. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  77. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  80. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  81. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  82. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  83. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  84. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  85. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  86. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  87. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  88. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  89. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  90. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  91. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  92. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  93. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  94. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  95. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  96. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  97. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  98. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  99. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  100. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  101. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  102. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  103. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  104. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  105. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  106. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  107. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  108. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  109. mx_bluesky/hyperion/log.py +99 -0
  110. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  111. mx_bluesky/hyperion/parameters/cli.py +68 -0
  112. mx_bluesky/hyperion/parameters/components.py +253 -0
  113. mx_bluesky/hyperion/parameters/constants.py +158 -0
  114. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  115. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  116. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  117. mx_bluesky/hyperion/tracing.py +28 -0
  118. mx_bluesky/hyperion/utils/context.py +84 -0
  119. mx_bluesky/hyperion/utils/utils.py +25 -0
  120. mx_bluesky/hyperion/utils/validation.py +196 -0
  121. mx_bluesky/jupyter_example.ipynb +3 -2
  122. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +53 -32
  123. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  124. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  125. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  126. mx_bluesky/I24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +0 -476
  127. mx_bluesky/I24/serial/fixed_target/FT-gui-edm/ME14E-motors.edl +0 -1874
  128. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +0 -706
  129. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -463
  130. mx_bluesky/I24/serial/parameters/__init__.py +0 -5
  131. mx_bluesky/I24/serial/parameters/constants.py +0 -39
  132. mx_bluesky/I24/serial/parameters/fixed_target/cs/cs_maker.json +0 -9
  133. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_1.txt +0 -4
  134. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_2.txt +0 -4
  135. mx_bluesky/I24/serial/parameters/fixed_target/litemaps/currentchip.map +0 -81
  136. mx_bluesky/I24/serial/parameters/fixed_target/parameters.txt +0 -13
  137. mx_bluesky/I24/serial/run_serial.py +0 -52
  138. mx_bluesky/I24/serial/write_nexus.py +0 -113
  139. mx_bluesky-0.0.2.dist-info/RECORD +0 -58
  140. mx_bluesky-0.0.2.dist-info/entry_points.txt +0 -4
  141. /mx_bluesky/{I24 → beamlines}/__init__.py +0 -0
  142. /mx_bluesky/{I24/serial → beamlines/i24}/__init__.py +0 -0
  143. /mx_bluesky/{I24 → beamlines/i24}/serial/extruder/__init__.py +0 -0
  144. /mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/__init__.py +0 -0
  145. /mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  146. /mx_bluesky/{I24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  147. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  148. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  149. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  150. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.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
+ )
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import json
5
+ from abc import abstractmethod
6
+ from collections.abc import Sequence
7
+ from enum import StrEnum
8
+ from pathlib import Path
9
+ from typing import SupportsInt, TypeVar
10
+
11
+ from dodal.devices.aperturescatterguard import ApertureValue
12
+ from dodal.devices.detector import (
13
+ DetectorParams,
14
+ TriggerMode,
15
+ )
16
+ from pydantic import (
17
+ BaseModel,
18
+ ConfigDict,
19
+ Field,
20
+ field_serializer,
21
+ field_validator,
22
+ model_validator,
23
+ )
24
+ from scanspec.core import AxesPoints
25
+ from semver import Version
26
+
27
+ from mx_bluesky.hyperion.external_interaction.config_server import FeatureFlags
28
+ from mx_bluesky.hyperion.parameters.constants import CONST
29
+
30
+ T = TypeVar("T")
31
+
32
+
33
+ class ParameterVersion(Version):
34
+ @classmethod
35
+ def _parse(cls, version):
36
+ if isinstance(version, cls):
37
+ return version
38
+ return cls.parse(version)
39
+
40
+
41
+ PARAMETER_VERSION = ParameterVersion.parse("5.1.0")
42
+
43
+
44
+ class RotationAxis(StrEnum):
45
+ OMEGA = "omega"
46
+ PHI = "phi"
47
+ CHI = "chi"
48
+ KAPPA = "kappa"
49
+
50
+
51
+ class XyzAxis(StrEnum):
52
+ X = "sam_x"
53
+ Y = "sam_y"
54
+ Z = "sam_z"
55
+
56
+
57
+ class IspybExperimentType(StrEnum):
58
+ # Enum values from ispyb column data type
59
+ SAD = "SAD" # at or slightly above the peak
60
+ SAD_INVERSE_BEAM = "SAD - Inverse Beam"
61
+ OSC = "OSC" # "native" (in the absence of a heavy atom)
62
+ COLLECT_MULTIWEDGE = (
63
+ "Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy???
64
+ )
65
+ MAD = "MAD"
66
+ HELICAL = "Helical"
67
+ MULTI_POSITIONAL = "Multi-positional"
68
+ MESH = "Mesh"
69
+ BURN = "Burn"
70
+ MAD_INVERSE_BEAM = "MAD - Inverse Beam"
71
+ CHARACTERIZATION = "Characterization"
72
+ DEHYDRATION = "Dehydration"
73
+ TOMO = "tomo"
74
+ EXPERIMENT = "experiment"
75
+ EM = "EM"
76
+ PDF = "PDF"
77
+ PDF_BRAGG = "PDF+Bragg"
78
+ BRAGG = "Bragg"
79
+ SINGLE_PARTICLE = "single particle"
80
+ SERIAL_FIXED = "Serial Fixed"
81
+ SERIAL_JET = "Serial Jet"
82
+ STANDARD = "Standard" # Routine structure determination experiment
83
+ TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time
84
+ DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell
85
+ CUSTOM = "Custom" # Special or non-standard data collection
86
+ XRF_MAP = "XRF map"
87
+ ENERGY_SCAN = "Energy scan"
88
+ XRF_SPECTRUM = "XRF spectrum"
89
+ XRF_MAP_XAS = "XRF map xas"
90
+ MESH_3D = "Mesh3D"
91
+ SCREENING = "Screening"
92
+ STILL = "Still"
93
+ SSX_CHIP = "SSX-Chip"
94
+ SSX_JET = "SSX-Jet"
95
+
96
+ # Aliases for historic hyperion experiment type mapping
97
+ ROTATION = "SAD"
98
+ GRIDSCAN_2D = "mesh"
99
+ GRIDSCAN_3D = "Mesh3D"
100
+
101
+
102
+ class HyperionParameters(BaseModel):
103
+ model_config = ConfigDict(
104
+ arbitrary_types_allowed=True,
105
+ extra="allow",
106
+ )
107
+
108
+ def __hash__(self) -> int:
109
+ return self.json().__hash__()
110
+
111
+ features: FeatureFlags = Field(default=FeatureFlags())
112
+ parameter_model_version: ParameterVersion
113
+
114
+ @field_serializer("parameter_model_version")
115
+ def serialize_parameter_version(self, version: ParameterVersion):
116
+ return str(version)
117
+
118
+ @field_validator("parameter_model_version", mode="before")
119
+ @classmethod
120
+ def _validate_version(cls, version_str: str):
121
+ version = ParameterVersion.parse(version_str)
122
+ assert (
123
+ version >= ParameterVersion(major=PARAMETER_VERSION.major)
124
+ ), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
125
+ assert (
126
+ version <= ParameterVersion(major=PARAMETER_VERSION.major + 1)
127
+ ), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
128
+ return version
129
+
130
+ @classmethod
131
+ def from_json(cls, input: str | None):
132
+ assert input is not None
133
+ return cls(**json.loads(input))
134
+
135
+
136
+ class WithSnapshot(BaseModel):
137
+ snapshot_directory: Path
138
+ snapshot_omegas_deg: list[float] | None = None
139
+
140
+ @property
141
+ def take_snapshots(self) -> bool:
142
+ return bool(self.snapshot_omegas_deg)
143
+
144
+
145
+ class DiffractionExperiment(HyperionParameters, WithSnapshot):
146
+ """For all experiments which use beam"""
147
+
148
+ visit: str = Field(min_length=1)
149
+ file_name: str
150
+ exposure_time_s: float = Field(gt=0)
151
+ comment: str = Field(default="")
152
+ beamline: str = Field(default=CONST.I03.BEAMLINE, pattern=r"BL\d{2}[BIJS]")
153
+ insertion_prefix: str = Field(
154
+ default=CONST.I03.INSERTION_PREFIX, pattern=r"SR\d{2}[BIJS]"
155
+ )
156
+ det_dist_to_beam_converter_path: str = Field(
157
+ default=CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
158
+ )
159
+ zocalo_environment: str = Field(default=CONST.ZOCALO_ENV)
160
+ trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
161
+ detector_distance_mm: float | None = Field(default=None, gt=0)
162
+ demand_energy_ev: float | None = Field(default=None, gt=0)
163
+ run_number: int | None = Field(default=None, ge=0)
164
+ selected_aperture: ApertureValue | None = Field(default=None)
165
+ transmission_frac: float = Field(default=0.1)
166
+ ispyb_experiment_type: IspybExperimentType
167
+ storage_directory: str
168
+
169
+ @model_validator(mode="before")
170
+ @classmethod
171
+ def validate_snapshot_directory(cls, values):
172
+ snapshot_dir = values.get(
173
+ "snapshot_directory", Path(values["storage_directory"], "snapshots")
174
+ )
175
+ values["snapshot_directory"] = (
176
+ snapshot_dir if isinstance(snapshot_dir, Path) else Path(snapshot_dir)
177
+ )
178
+ return values
179
+
180
+ @property
181
+ def visit_directory(self) -> Path:
182
+ return (
183
+ Path(CONST.I03.BASE_DATA_DIR) / str(datetime.date.today().year) / self.visit
184
+ )
185
+
186
+ @property
187
+ def num_images(self) -> int:
188
+ return 0
189
+
190
+ @property
191
+ @abstractmethod
192
+ def detector_params(self) -> DetectorParams: ...
193
+
194
+
195
+ class WithScan(BaseModel):
196
+ """For experiments where the scan is known"""
197
+
198
+ @property
199
+ @abstractmethod
200
+ def scan_points(self) -> AxesPoints: ...
201
+
202
+ @property
203
+ @abstractmethod
204
+ def num_images(self) -> int: ...
205
+
206
+
207
+ class SplitScan(BaseModel):
208
+ @property
209
+ @abstractmethod
210
+ def scan_indices(self) -> Sequence[SupportsInt]:
211
+ """Should return the first index of each scan (i.e. for each nexus file)"""
212
+ ...
213
+
214
+
215
+ class WithSample(BaseModel):
216
+ sample_id: int
217
+ sample_puck: int | None = None
218
+ sample_pin: int | None = None
219
+
220
+
221
+ class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
222
+
223
+
224
+ class WithOavCentring(BaseModel):
225
+ oav_centring_file: str = Field(default=CONST.I03.OAV_CENTRING_FILE)
226
+
227
+
228
+ class OptionalXyzStarts(BaseModel):
229
+ x_start_um: float | None = None
230
+ y_start_um: float | None = None
231
+ z_start_um: float | None = None
232
+
233
+
234
+ class XyzStarts(BaseModel):
235
+ x_start_um: float
236
+ y_start_um: float
237
+ z_start_um: float
238
+
239
+ def _start_for_axis(self, axis: XyzAxis) -> float:
240
+ match axis:
241
+ case XyzAxis.X:
242
+ return self.x_start_um
243
+ case XyzAxis.Y:
244
+ return self.y_start_um
245
+ case XyzAxis.Z:
246
+ return self.z_start_um
247
+
248
+
249
+ class OptionalGonioAngleStarts(BaseModel):
250
+ omega_start_deg: float | None = None
251
+ phi_start_deg: float | None = None
252
+ chi_start_deg: float | None = None
253
+ kappa_start_deg: float | None = None
@@ -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,216 @@
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
+ WithScan,
24
+ XyzStarts,
25
+ )
26
+ from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
27
+
28
+
29
+ class GridCommon(
30
+ DiffractionExperimentWithSample, OptionalGonioAngleStarts, WithOavCentring
31
+ ):
32
+ grid_width_um: float = Field(default=CONST.PARAM.GRIDSCAN.WIDTH_UM)
33
+ exposure_time_s: float = Field(default=CONST.PARAM.GRIDSCAN.EXPOSURE_TIME_S)
34
+ use_roi_mode: bool = Field(default=CONST.PARAM.GRIDSCAN.USE_ROI)
35
+ panda_runup_distance_mm: float = Field(
36
+ default=CONST.HARDWARE.PANDA_FGS_RUN_UP_DEFAULT
37
+ )
38
+ use_panda: bool = Field(default=CONST.I03.USE_PANDA_FOR_GRIDSCAN)
39
+ use_gpu: bool = Field(default=CONST.I03.USE_GPU_FOR_GRIDSCAN_ANALYSIS)
40
+ use_cpu_and_gpu_zocalo: bool = Field(default=CONST.I03.USE_CPU_AND_GPU_ZOCALO)
41
+ ispyb_experiment_type: IspybExperimentType = Field(
42
+ default=IspybExperimentType.GRIDSCAN_3D
43
+ )
44
+ selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL)
45
+
46
+ @property
47
+ def detector_params(self):
48
+ self.det_dist_to_beam_converter_path = (
49
+ self.det_dist_to_beam_converter_path
50
+ or CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
51
+ )
52
+ optional_args = {}
53
+ if self.run_number:
54
+ optional_args["run_number"] = self.run_number
55
+ assert (
56
+ self.detector_distance_mm is not None
57
+ ), "Detector distance must be filled before generating DetectorParams"
58
+ os.makedirs(self.storage_directory, exist_ok=True)
59
+ return DetectorParams(
60
+ detector_size_constants=I03Constants.DETECTOR,
61
+ expected_energy_ev=self.demand_energy_ev,
62
+ exposure_time=self.exposure_time_s,
63
+ directory=self.storage_directory,
64
+ prefix=self.file_name,
65
+ detector_distance=self.detector_distance_mm,
66
+ omega_start=self.omega_start_deg or 0,
67
+ omega_increment=0,
68
+ num_images_per_trigger=1,
69
+ num_triggers=self.num_images,
70
+ use_roi_mode=self.use_roi_mode,
71
+ det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
72
+ trigger_mode=self.trigger_mode,
73
+ enable_dev_shm=self.use_gpu,
74
+ **optional_args,
75
+ )
76
+
77
+
78
+ class GridScanWithEdgeDetect(GridCommon): ...
79
+
80
+
81
+ class PinTipCentreThenXrayCentre(GridCommon):
82
+ tip_offset_um: float = 0
83
+
84
+
85
+ class RobotLoadThenCentre(GridCommon):
86
+ thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
87
+
88
+ def pin_centre_then_xray_centre_params(self):
89
+ my_params = self.model_dump()
90
+ del my_params["thawing_time"]
91
+ return PinTipCentreThenXrayCentre(**my_params)
92
+
93
+
94
+ class SpecifiedGridScan(GridCommon, XyzStarts, WithScan):
95
+ """A specified grid scan is one which has defined values for the start position,
96
+ grid and box sizes, etc., as opposed to parameters for a plan which will create
97
+ those parameters at some point (e.g. through optical pin detection)."""
98
+
99
+ ...
100
+
101
+
102
+ class ThreeDGridScan(SpecifiedGridScan, SplitScan):
103
+ """Parameters representing a so-called 3D grid scan, which consists of doing a
104
+ gridscan in X and Y, followed by one in X and Z."""
105
+
106
+ demand_energy_ev: float | None = Field(default=None)
107
+ grid1_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_1) # type: ignore
108
+ grid2_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_2)
109
+ x_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
110
+ y_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
111
+ z_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
112
+ y2_start_um: float
113
+ z2_start_um: float
114
+ x_steps: int = Field(gt=0)
115
+ y_steps: int = Field(gt=0)
116
+ z_steps: int = Field(gt=0)
117
+ _set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
118
+
119
+ @property
120
+ def FGS_params(self) -> ZebraGridScanParams:
121
+ return ZebraGridScanParams(
122
+ x_steps=self.x_steps,
123
+ y_steps=self.y_steps,
124
+ z_steps=self.z_steps,
125
+ x_step_size=self.x_step_size_um,
126
+ y_step_size=self.y_step_size_um,
127
+ z_step_size=self.z_step_size_um,
128
+ x_start=self.x_start_um,
129
+ y1_start=self.y_start_um,
130
+ z1_start=self.z_start_um,
131
+ y2_start=self.y2_start_um,
132
+ z2_start=self.z2_start_um,
133
+ set_stub_offsets=self.features.set_stub_offsets,
134
+ dwell_time_ms=self.exposure_time_s * 1000,
135
+ transmission_fraction=self.transmission_frac,
136
+ )
137
+
138
+ @property
139
+ def panda_FGS_params(self) -> PandAGridScanParams:
140
+ if self.y_steps % 2 and self.z_steps > 0:
141
+ raise OddYStepsException(
142
+ "The number of Y steps must be even for a PandA gridscan"
143
+ )
144
+ return PandAGridScanParams(
145
+ x_steps=self.x_steps,
146
+ y_steps=self.y_steps,
147
+ z_steps=self.z_steps,
148
+ x_step_size=self.x_step_size_um,
149
+ y_step_size=self.y_step_size_um,
150
+ z_step_size=self.z_step_size_um,
151
+ x_start=self.x_start_um,
152
+ y1_start=self.y_start_um,
153
+ z1_start=self.z_start_um,
154
+ y2_start=self.y2_start_um,
155
+ z2_start=self.z2_start_um,
156
+ set_stub_offsets=self.features.set_stub_offsets,
157
+ run_up_distance_mm=self.panda_runup_distance_mm,
158
+ transmission_fraction=self.transmission_frac,
159
+ )
160
+
161
+ def do_set_stub_offsets(self, value: bool):
162
+ self._set_stub_offsets = value
163
+
164
+ @property
165
+ def grid_1_spec(self):
166
+ x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
167
+ y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
168
+ grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
169
+ grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
170
+ grid_1_z = Static("sam_z", self.z_start_um)
171
+ return grid_1_y.zip(grid_1_z) * ~grid_1_x
172
+
173
+ @property
174
+ def grid_2_spec(self):
175
+ x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
176
+ z2_end = self.z2_start_um + self.z_step_size_um * (self.z_steps - 1)
177
+ grid_2_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
178
+ grid_2_z = Line("sam_z", self.z2_start_um, z2_end, self.z_steps)
179
+ grid_2_y = Static("sam_y", self.y2_start_um)
180
+ return grid_2_z.zip(grid_2_y) * ~grid_2_x
181
+
182
+ @property
183
+ def scan_indices(self):
184
+ """The first index of each gridscan, useful for writing nexus files/VDS"""
185
+ return [
186
+ 0,
187
+ len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
188
+ ]
189
+
190
+ @property
191
+ def scan_spec(self):
192
+ """A fully specified ScanSpec object representing both grids, with x, y, z and
193
+ omega positions."""
194
+ return self.grid_1_spec.concat(self.grid_2_spec)
195
+
196
+ @property
197
+ def scan_points(self):
198
+ """A list of all the points in the scan_spec."""
199
+ return ScanPath(self.scan_spec.calculate()).consume().midpoints
200
+
201
+ @property
202
+ def scan_points_first_grid(self):
203
+ """A list of all the points in the first grid scan."""
204
+ return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
205
+
206
+ @property
207
+ def scan_points_second_grid(self):
208
+ """A list of all the points in the second grid scan."""
209
+ return ScanPath(self.grid_2_spec.calculate()).consume().midpoints
210
+
211
+ @property
212
+ def num_images(self) -> int:
213
+ return len(self.scan_points["sam_x"])
214
+
215
+
216
+ class OddYStepsException(Exception): ...