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,253 +1,12 @@
1
1
  from __future__ import annotations
2
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
3
  from pydantic import (
17
4
  BaseModel,
18
- ConfigDict,
19
5
  Field,
20
- field_serializer,
21
- field_validator,
22
- model_validator,
23
6
  )
24
- from scanspec.core import AxesPoints
25
- from semver import Version
26
7
 
27
8
  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
9
 
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
10
 
11
+ class WithFeatures(BaseModel):
111
12
  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
@@ -1,107 +1,21 @@
1
1
  import os
2
- from enum import Enum
3
2
 
4
- from dodal.devices.aperturescatterguard import ApertureValue
5
3
  from dodal.devices.detector import EIGER2_X_16M_SIZE
6
4
  from pydantic.dataclasses import dataclass
7
5
 
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
-
6
+ from mx_bluesky.common.parameters.constants import (
7
+ DocDescriptorNames,
8
+ EnvironmentConstants,
9
+ ExperimentParamConstants,
10
+ HardwareConstants,
11
+ OavConstants,
12
+ PlanGroupCheckpointConstants,
13
+ PlanNameConstants,
14
+ SimConstants,
15
+ TriggerConstants,
16
+ )
86
17
 
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"
18
+ TEST_MODE = os.environ.get("HYPERION_TEST_MODE")
105
19
 
106
20
 
107
21
  @dataclass(frozen=True)
@@ -110,16 +24,22 @@ class I03Constants:
110
24
  BEAMLINE = "BL03S" if TEST_MODE else "BL03I"
111
25
  DETECTOR = EIGER2_X_16M_SIZE
112
26
  INSERTION_PREFIX = "SR03S" if TEST_MODE else "SR03I"
113
- OAV_CENTRING_FILE = _test_oav_file if TEST_MODE else _live_oav_file
27
+ OAV_CENTRING_FILE = OavConstants.OAV_CONFIG_JSON
114
28
  SHUTTER_TIME_S = 0.06
115
29
  USE_PANDA_FOR_GRIDSCAN = False
116
- USE_GPU_FOR_GRIDSCAN_ANALYSIS = False
117
30
  THAWING_TIME = 20
118
- USE_CPU_AND_GPU_ZOCALO = False
31
+ SET_STUB_OFFSETS = False
32
+
33
+ # Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
34
+ # processed results. GPU results never used in analysis for now
35
+ COMPARE_CPU_AND_GPU_ZOCALO = False
119
36
 
120
37
 
121
38
  @dataclass(frozen=True)
122
39
  class HyperionConstants:
40
+ DESCRIPTORS = DocDescriptorNames()
41
+ TRIGGER = TriggerConstants()
42
+ ZOCALO_ENV = EnvironmentConstants.ZOCALO_ENV
123
43
  HARDWARE = HardwareConstants()
124
44
  I03 = I03Constants()
125
45
  PARAM = ExperimentParamConstants()
@@ -136,23 +56,7 @@ class HyperionConstants:
136
56
  )
137
57
  GRAYLOG_PORT = 12232
138
58
  PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
139
- ZOCALO_ENV = "dev_artemis" if TEST_MODE else "artemis"
59
+ LOG_FILE_NAME = "hyperion.log"
140
60
 
141
61
 
142
62
  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"
@@ -14,30 +14,35 @@ from pydantic import Field, PrivateAttr
14
14
  from scanspec.core import Path as ScanPath
15
15
  from scanspec.specs import Line, Static
16
16
 
17
- from mx_bluesky.hyperion.parameters.components import (
17
+ from mx_bluesky.common.parameters.components import (
18
18
  DiffractionExperimentWithSample,
19
19
  IspybExperimentType,
20
20
  OptionalGonioAngleStarts,
21
21
  SplitScan,
22
- WithOavCentring,
22
+ WithOptionalEnergyChange,
23
23
  WithScan,
24
24
  XyzStarts,
25
25
  )
26
+ from mx_bluesky.common.parameters.constants import GridscanParamConstants
27
+ from mx_bluesky.hyperion.parameters.components import WithFeatures
26
28
  from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
29
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
27
30
 
28
31
 
32
+ # This will be restructed once Once https://github.com/DiamondLightSource/mx-bluesky/issues/323#issue-2500957290 is further along
33
+ # to handle slightly different parameters between different beamline implementations
29
34
  class GridCommon(
30
- DiffractionExperimentWithSample, OptionalGonioAngleStarts, WithOavCentring
35
+ DiffractionExperimentWithSample,
36
+ OptionalGonioAngleStarts,
37
+ WithFeatures,
31
38
  ):
32
39
  grid_width_um: float = Field(default=CONST.PARAM.GRIDSCAN.WIDTH_UM)
33
40
  exposure_time_s: float = Field(default=CONST.PARAM.GRIDSCAN.EXPOSURE_TIME_S)
34
41
  use_roi_mode: bool = Field(default=CONST.PARAM.GRIDSCAN.USE_ROI)
35
42
  panda_runup_distance_mm: float = Field(
36
- default=CONST.HARDWARE.PANDA_FGS_RUN_UP_DEFAULT
43
+ default=GridscanParamConstants.PANDA_RUN_UP_DISTANCE_MM
37
44
  )
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)
45
+
41
46
  ispyb_experiment_type: IspybExperimentType = Field(
42
47
  default=IspybExperimentType.GRIDSCAN_3D
43
48
  )
@@ -70,12 +75,13 @@ class GridCommon(
70
75
  use_roi_mode=self.use_roi_mode,
71
76
  det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
72
77
  trigger_mode=self.trigger_mode,
73
- enable_dev_shm=self.use_gpu,
78
+ enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo,
74
79
  **optional_args,
75
80
  )
76
81
 
77
82
 
78
- class GridScanWithEdgeDetect(GridCommon): ...
83
+ class GridScanWithEdgeDetect(GridCommon):
84
+ box_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
79
85
 
80
86
 
81
87
  class PinTipCentreThenXrayCentre(GridCommon):
@@ -85,6 +91,10 @@ class PinTipCentreThenXrayCentre(GridCommon):
85
91
  class RobotLoadThenCentre(GridCommon):
86
92
  thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
87
93
 
94
+ def robot_load_params(self):
95
+ my_params = self.model_dump()
96
+ return RobotLoadAndEnergyChange(**my_params)
97
+
88
98
  def pin_centre_then_xray_centre_params(self):
89
99
  my_params = self.model_dump()
90
100
  del my_params["thawing_time"]
@@ -99,11 +109,10 @@ class SpecifiedGridScan(GridCommon, XyzStarts, WithScan):
99
109
  ...
100
110
 
101
111
 
102
- class ThreeDGridScan(SpecifiedGridScan, SplitScan):
112
+ class ThreeDGridScan(SpecifiedGridScan, SplitScan, WithOptionalEnergyChange):
103
113
  """Parameters representing a so-called 3D grid scan, which consists of doing a
104
114
  gridscan in X and Y, followed by one in X and Z."""
105
115
 
106
- demand_energy_ev: float | None = Field(default=None)
107
116
  grid1_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_1) # type: ignore
108
117
  grid2_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_2)
109
118
  x_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
@@ -0,0 +1,50 @@
1
+ from typing import TypeVar
2
+
3
+ from pydantic import BaseModel, model_validator
4
+
5
+ from mx_bluesky.common.parameters.components import (
6
+ MxBlueskyParameters,
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(MxBlueskyParameters, 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.common.parameters.components import (
4
+ MxBlueskyParameters,
5
+ WithOptionalEnergyChange,
6
+ WithSample,
7
+ WithSnapshot,
8
+ WithVisit,
9
+ )
10
+ from mx_bluesky.hyperion.parameters.constants import CONST
11
+
12
+
13
+ class RobotLoadAndEnergyChange(
14
+ MxBlueskyParameters, WithSample, WithSnapshot, WithOptionalEnergyChange, WithVisit
15
+ ):
16
+ thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
@@ -17,7 +17,7 @@ from scanspec.core import AxesPoints
17
17
  from scanspec.core import Path as ScanPath
18
18
  from scanspec.specs import Line
19
19
 
20
- from mx_bluesky.hyperion.parameters.components import (
20
+ from mx_bluesky.common.parameters.components import (
21
21
  DiffractionExperimentWithSample,
22
22
  IspybExperimentType,
23
23
  OptionalGonioAngleStarts,
@@ -115,12 +115,16 @@ class MultiRotationScan(RotationExperiment, SplitScan):
115
115
 
116
116
  def _single_rotation_scan(self, scan: RotationScanPerSweep) -> RotationScan:
117
117
  # self has everything from RotationExperiment
118
- params = self.model_dump()
119
- del params["rotation_scans"]
118
+ allowed_keys = RotationScan.model_fields.keys()
119
+ params_dump = self.model_dump()
120
120
  # provided `scan` has everything from RotationScanPerSweep
121
- params.update(scan.model_dump())
121
+ scan_dump = scan.model_dump()
122
+ rotation_scan_kv_pairs = {
123
+ k: v for k, v in (params_dump | scan_dump).items() if k in allowed_keys
124
+ }
122
125
  # together they have everything for RotationScan
123
- return RotationScan(**params)
126
+ rotation_scan = RotationScan(**rotation_scan_kv_pairs)
127
+ return rotation_scan
124
128
 
125
129
  @model_validator(mode="after")
126
130
  @classmethod
@@ -1,3 +1,6 @@
1
+ import math
2
+ from math import asin
3
+
1
4
  from scanspec.core import AxesPoints, Axis
2
5
  from scipy.constants import physical_constants
3
6
 
@@ -23,3 +26,17 @@ def convert_angstrom_to_eV(wavelength: float) -> float:
23
26
  def number_of_frames_from_scan_spec(scan_points: AxesPoints[Axis]):
24
27
  ax = list(scan_points.keys())[0]
25
28
  return len(scan_points[ax])
29
+
30
+
31
+ def energy_to_bragg_angle(energy_kev: float, d_a: float) -> float:
32
+ """Compute the bragg angle given the energy in kev.
33
+
34
+ Args:
35
+ energy_kev: The energy in keV
36
+ d_a: The lattice spacing in Angstroms
37
+ Returns:
38
+ The bragg angle in degrees
39
+ """
40
+ wavelength_a = convert_eV_to_angstrom(energy_kev * 1000)
41
+ d = d_a
42
+ return asin(wavelength_a / (2 * d)) * 180 / math.pi
@@ -8,7 +8,7 @@ from unittest.mock import patch
8
8
  import bluesky.preprocessors as bpp
9
9
  from bluesky.run_engine import RunEngine
10
10
  from dodal.beamlines import i03
11
- from dodal.devices.oav.oav_parameters import OAVConfigParams
11
+ from dodal.devices.oav.oav_parameters import OAVConfig
12
12
  from ophyd_async.core import set_mock_value
13
13
 
14
14
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
@@ -23,8 +23,8 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback
23
23
  from mx_bluesky.hyperion.parameters.constants import CONST
24
24
  from mx_bluesky.hyperion.parameters.rotation import RotationScan
25
25
 
26
- DISPLAY_CONFIGURATION = "tests/devices/unit_tests/test_display.configuration"
27
- ZOOM_LEVELS_XML = "tests/devices/unit_tests/test_jCameraManZoomLevels.xml"
26
+ DISPLAY_CONFIGURATION = "tests/test_data/test_display.configuration"
27
+ ZOOM_LEVELS_XML = "tests/test_data/test_jCameraManZoomLevels.xml"
28
28
  TEST_DATA_DIRECTORY = Path("tests/test_data/nexus_files/rotation")
29
29
  TEST_METAFILE = "ins_8_5_meta.h5.gz"
30
30
  FAKE_DATAFILE = "../fake_data.h5"
@@ -94,15 +94,14 @@ def fake_create_rotation_devices():
94
94
  robot = i03.robot(fake_with_ophyd_sim=True)
95
95
  oav = i03.oav(
96
96
  fake_with_ophyd_sim=True,
97
- params=OAVConfigParams(
98
- zoom_params_file=ZOOM_LEVELS_XML, display_config=DISPLAY_CONFIGURATION
97
+ params=OAVConfig(
98
+ zoom_params_file=ZOOM_LEVELS_XML, display_config_file=DISPLAY_CONFIGURATION
99
99
  ),
100
100
  )
101
101
  xbpm_feedback = i03.xbpm_feedback(fake_with_ophyd_sim=True)
102
102
 
103
103
  set_mock_value(smargon.omega.max_velocity, 131)
104
104
  set_mock_value(dcm.energy_in_kev.user_readback, 12700)
105
- oav.zoom_controller.fvst.sim_put("1.0x") # type: ignore
106
105
 
107
106
  return RotationScanComposite(
108
107
  attenuator=attenuator,