mx-bluesky 0.3.1__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 (138) 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 +44 -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 +84 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  50. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  51. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  53. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  54. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  55. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  56. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  57. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  58. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  78. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  79. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  80. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  81. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  85. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  86. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  88. mx_bluesky/hyperion/log.py +99 -0
  89. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  90. mx_bluesky/hyperion/parameters/cli.py +68 -0
  91. mx_bluesky/{parameters → hyperion/parameters}/components.py +77 -24
  92. mx_bluesky/hyperion/parameters/constants.py +158 -0
  93. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  94. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  95. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  96. mx_bluesky/hyperion/tracing.py +28 -0
  97. mx_bluesky/hyperion/utils/context.py +84 -0
  98. mx_bluesky/hyperion/utils/utils.py +25 -0
  99. mx_bluesky/hyperion/utils/validation.py +196 -0
  100. mx_bluesky/jupyter_example.ipynb +3 -2
  101. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +26 -11
  102. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  103. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  104. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  105. mx_bluesky/i04/__init__.py +0 -3
  106. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  107. mx_bluesky/parameters/__init__.py +0 -31
  108. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  109. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  110. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  111. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  112. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  113. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  114. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  115. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  116. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  119. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  137. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  138. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,174 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
7
+ populate_data_collection_group,
8
+ populate_remaining_data_collection_info,
9
+ )
10
+ from mx_bluesky.hyperion.external_interaction.callbacks.ispyb_callback_base import (
11
+ BaseISPyBCallback,
12
+ )
13
+ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping import (
14
+ populate_data_collection_info_for_rotation,
15
+ )
16
+ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
17
+ DataCollectionInfo,
18
+ DataCollectionPositionInfo,
19
+ ScanDataInfo,
20
+ )
21
+ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
22
+ IspybIds,
23
+ StoreInIspyb,
24
+ )
25
+ from mx_bluesky.hyperion.log import ISPYB_LOGGER, set_dcgid_tag
26
+ from mx_bluesky.hyperion.parameters.components import IspybExperimentType
27
+ from mx_bluesky.hyperion.parameters.constants import CONST
28
+ from mx_bluesky.hyperion.parameters.rotation import RotationScan
29
+
30
+ if TYPE_CHECKING:
31
+ from event_model.documents import Event, RunStart, RunStop
32
+
33
+
34
+ class RotationISPyBCallback(BaseISPyBCallback):
35
+ """Callback class to handle the deposition of experiment parameters into the ISPyB
36
+ database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
37
+ recieving an 'event' document for the 'ispyb_reading_hardware' event, and updates the
38
+ deposition on recieving its final 'stop' document.
39
+
40
+ To use, subscribe the Bluesky RunEngine to an instance of this class.
41
+ E.g.:
42
+ ispyb_handler_callback = RotationISPyBCallback(parameters)
43
+ RE.subscribe(ispyb_handler_callback)
44
+ Or decorate a plan using bluesky.preprocessors.subs_decorator.
45
+
46
+ See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ *,
52
+ emit: Callable[..., Any] | None = None,
53
+ ) -> None:
54
+ super().__init__(emit=emit)
55
+ self.last_sample_id: int | None = None
56
+ self.ispyb_ids: IspybIds = IspybIds()
57
+
58
+ def activity_gated_start(self, doc: RunStart):
59
+ if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
60
+ ISPYB_LOGGER.info(
61
+ "ISPyB callback received start document with experiment parameters."
62
+ )
63
+ self.params = RotationScan.from_json(doc.get("hyperion_parameters"))
64
+ dcgid = (
65
+ self.ispyb_ids.data_collection_group_id
66
+ if (self.params.sample_id == self.last_sample_id)
67
+ else None
68
+ )
69
+ if (
70
+ self.params.ispyb_experiment_type
71
+ == IspybExperimentType.CHARACTERIZATION
72
+ ):
73
+ ISPYB_LOGGER.info("Screening collection - using new DCG")
74
+ dcgid = None
75
+ self.last_sample_id = None
76
+ else:
77
+ ISPYB_LOGGER.info(
78
+ f"Collection is {self.params.ispyb_experiment_type} - storing sampleID to bundle images"
79
+ )
80
+ self.last_sample_id = self.params.sample_id
81
+ self.ispyb = StoreInIspyb(self.ispyb_config)
82
+ ISPYB_LOGGER.info("Beginning ispyb deposition")
83
+ data_collection_group_info = populate_data_collection_group(self.params)
84
+ data_collection_info = populate_data_collection_info_for_rotation(
85
+ cast(RotationScan, self.params)
86
+ )
87
+ data_collection_info = populate_remaining_data_collection_info(
88
+ self.params.comment,
89
+ dcgid,
90
+ data_collection_info,
91
+ self.params,
92
+ )
93
+ data_collection_info.parent_id = dcgid
94
+ scan_data_info = ScanDataInfo(
95
+ data_collection_info=data_collection_info,
96
+ )
97
+ self.ispyb_ids = self.ispyb.begin_deposition(
98
+ data_collection_group_info, [scan_data_info]
99
+ )
100
+ ISPYB_LOGGER.info("ISPYB handler received start document.")
101
+ if doc.get("subplan_name") == CONST.PLAN.ROTATION_MAIN:
102
+ self.uid_to_finalize_on = doc.get("uid")
103
+ return super().activity_gated_start(doc)
104
+
105
+ def populate_info_for_update(
106
+ self,
107
+ event_sourced_data_collection_info: DataCollectionInfo,
108
+ event_sourced_position_info: DataCollectionPositionInfo | None,
109
+ params,
110
+ ) -> Sequence[ScanDataInfo]:
111
+ assert (
112
+ self.ispyb_ids.data_collection_ids
113
+ ), "Expect an existing DataCollection to update"
114
+
115
+ return [
116
+ ScanDataInfo(
117
+ data_collection_info=event_sourced_data_collection_info,
118
+ data_collection_id=self.ispyb_ids.data_collection_ids[0],
119
+ data_collection_position_info=event_sourced_position_info,
120
+ )
121
+ ]
122
+
123
+ def _handle_ispyb_hardware_read(self, doc: Event):
124
+ """Use the hardware read values to create the ispyb comment"""
125
+ scan_data_infos = super()._handle_ispyb_hardware_read(doc)
126
+ motor_positions_mm = [
127
+ doc["data"]["smargon-x"],
128
+ doc["data"]["smargon-y"],
129
+ doc["data"]["smargon-z"],
130
+ ]
131
+ assert (
132
+ self.params
133
+ ), "handle_ispyb_hardware_read triggered before activity_gated_start"
134
+ motor_positions_um = [position * 1000 for position in motor_positions_mm]
135
+ comment = f"Sample position (µm): ({motor_positions_um[0]:.0f}, {motor_positions_um[1]:.0f}, {motor_positions_um[2]:.0f}) {self.params.comment} "
136
+ scan_data_infos[0].data_collection_info.comments = comment
137
+ return scan_data_infos
138
+
139
+ def activity_gated_event(self, doc: Event):
140
+ doc = super().activity_gated_event(doc)
141
+ set_dcgid_tag(self.ispyb_ids.data_collection_group_id)
142
+
143
+ descriptor_name = self.descriptors[doc["descriptor"]].get("name")
144
+ if descriptor_name == CONST.DESCRIPTORS.OAV_ROTATION_SNAPSHOT_TRIGGERED:
145
+ scan_data_infos = self._handle_oav_rotation_snapshot_triggered(doc)
146
+ self.ispyb_ids = self.ispyb.update_deposition(
147
+ self.ispyb_ids, scan_data_infos
148
+ )
149
+
150
+ return doc
151
+
152
+ def _handle_oav_rotation_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]:
153
+ assert self.ispyb_ids.data_collection_ids, "No current data collection"
154
+ assert self.params, "ISPyB handler didn't receive parameters!"
155
+ data = doc["data"]
156
+ self._oav_snapshot_event_idx += 1
157
+ data_collection_info = DataCollectionInfo(
158
+ **{
159
+ f"xtal_snapshot{self._oav_snapshot_event_idx}": data.get(
160
+ "oav_snapshot_last_saved_path"
161
+ )
162
+ }
163
+ )
164
+ scan_data_info = ScanDataInfo(
165
+ data_collection_id=self.ispyb_ids.data_collection_ids[-1],
166
+ data_collection_info=data_collection_info,
167
+ )
168
+ return [scan_data_info]
169
+
170
+ def activity_gated_stop(self, doc: RunStop) -> RunStop:
171
+ if doc.get("run_start") == self.uid_to_finalize_on:
172
+ self.uid_to_finalize_on = None
173
+ return super().activity_gated_stop(doc)
174
+ return self._tag_doc(doc)
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import DataCollectionInfo
4
+ from mx_bluesky.hyperion.parameters.rotation import RotationScan
5
+
6
+
7
+ def populate_data_collection_info_for_rotation(params: RotationScan):
8
+ info = DataCollectionInfo(
9
+ omega_start=params.omega_start_deg,
10
+ data_collection_number=params.detector_params.run_number, # type:ignore # the validator always makes this int
11
+ n_images=params.num_images,
12
+ axis_range=params.rotation_increment_deg,
13
+ axis_start=params.omega_start_deg,
14
+ axis_end=(params.omega_start_deg + params.scan_width_deg),
15
+ kappa_start=params.kappa_start_deg,
16
+ )
17
+ return info
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
6
+ PlanReactiveCallback,
7
+ )
8
+ from mx_bluesky.hyperion.external_interaction.nexus.nexus_utils import (
9
+ create_beam_and_attenuator_parameters,
10
+ vds_type_based_on_bit_depth,
11
+ )
12
+ from mx_bluesky.hyperion.external_interaction.nexus.write_nexus import NexusWriter
13
+ from mx_bluesky.hyperion.log import NEXUS_LOGGER
14
+ from mx_bluesky.hyperion.parameters.constants import CONST
15
+ from mx_bluesky.hyperion.parameters.rotation import RotationScan
16
+
17
+ from ..logging_callback import format_doc_for_log
18
+
19
+ if TYPE_CHECKING:
20
+ from event_model.documents import Event, EventDescriptor, RunStart
21
+
22
+
23
+ class RotationNexusFileCallback(PlanReactiveCallback):
24
+ """Callback class to handle the creation of Nexus files based on experiment
25
+ parameters for rotation scans
26
+
27
+ To use, subscribe the Bluesky RunEngine to an instance of this class.
28
+ E.g.:
29
+ nexus_file_handler_callback = NexusFileCallback(parameters)
30
+ RE.subscribe(nexus_file_handler_callback)
31
+ Or decorate a plan using bluesky.preprocessors.subs_decorator.
32
+
33
+ See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
34
+ """
35
+
36
+ def __init__(self) -> None:
37
+ super().__init__(NEXUS_LOGGER)
38
+ self.run_uid: str | None = None
39
+ self.writer: NexusWriter | None = None
40
+ self.descriptors: dict[str, EventDescriptor] = {}
41
+ # used when multiple collections are made in one detector arming event:
42
+ self.full_num_of_images: int | None = None
43
+ self.meta_data_run_number: int | None = None
44
+
45
+ def activity_gated_descriptor(self, doc: EventDescriptor):
46
+ self.descriptors[doc["uid"]] = doc
47
+
48
+ def activity_gated_event(self, doc: Event):
49
+ event_descriptor = self.descriptors.get(doc["descriptor"])
50
+ if event_descriptor is None:
51
+ NEXUS_LOGGER.warning(
52
+ f"Rotation Nexus handler {self} received event doc {format_doc_for_log(doc)} and "
53
+ "has no corresponding descriptor record"
54
+ )
55
+ return doc
56
+ if event_descriptor.get("name") == CONST.DESCRIPTORS.HARDWARE_READ_DURING:
57
+ NEXUS_LOGGER.info(
58
+ f"Nexus handler received event from read hardware {format_doc_for_log(doc)}"
59
+ )
60
+ data = doc["data"]
61
+ assert self.writer, "Nexus writer not initialised"
62
+ (
63
+ self.writer.beam,
64
+ self.writer.attenuator,
65
+ ) = create_beam_and_attenuator_parameters(
66
+ data["dcm-energy_in_kev"],
67
+ data["flux_flux_reading"],
68
+ data["attenuator-actual_transmission"],
69
+ )
70
+ vds_data_type = vds_type_based_on_bit_depth(doc["data"]["eiger_bit_depth"])
71
+ self.writer.create_nexus_file(vds_data_type)
72
+ NEXUS_LOGGER.info(f"Nexus file created at {self.writer.data_filename}")
73
+ return doc
74
+
75
+ def activity_gated_start(self, doc: RunStart):
76
+ if doc.get("subplan_name") == CONST.PLAN.ROTATION_MULTI:
77
+ self.full_num_of_images = doc.get("full_num_of_images")
78
+ self.meta_data_run_number = doc.get("meta_data_run_number")
79
+ if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
80
+ self.run_uid = doc.get("uid")
81
+ json_params = doc.get("hyperion_parameters")
82
+ NEXUS_LOGGER.info(
83
+ f"Nexus writer received start document with experiment parameters {json_params}"
84
+ )
85
+ parameters = RotationScan.from_json(json_params)
86
+ NEXUS_LOGGER.info("Setting up nexus file...")
87
+ det_size = (
88
+ parameters.detector_params.detector_size_constants.det_size_pixels
89
+ )
90
+ shape = (parameters.num_images, det_size.width, det_size.height)
91
+ self.writer = NexusWriter(
92
+ parameters,
93
+ shape,
94
+ parameters.scan_points,
95
+ omega_start_deg=parameters.omega_start_deg,
96
+ chi_start_deg=parameters.chi_start_deg or 0,
97
+ phi_start_deg=parameters.phi_start_deg or 0,
98
+ vds_start_index=parameters.nexus_vds_start_img,
99
+ full_num_of_images=self.full_num_of_images,
100
+ meta_data_run_number=self.meta_data_run_number,
101
+ rotation_direction=parameters.rotation_direction,
102
+ )
@@ -0,0 +1,269 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from time import time
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import numpy as np
8
+ from blueapi.core import MsgGenerator
9
+ from bluesky import preprocessors as bpp
10
+ from dodal.devices.zocalo.zocalo_results import ZOCALO_READING_PLAN_NAME
11
+
12
+ from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
13
+ populate_data_collection_group,
14
+ populate_remaining_data_collection_info,
15
+ )
16
+ from mx_bluesky.hyperion.external_interaction.callbacks.ispyb_callback_base import (
17
+ BaseISPyBCallback,
18
+ )
19
+ from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import (
20
+ format_doc_for_log,
21
+ )
22
+ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_mapping import (
23
+ construct_comment_for_gridscan,
24
+ populate_xy_data_collection_info,
25
+ populate_xz_data_collection_info,
26
+ )
27
+ from mx_bluesky.hyperion.external_interaction.exceptions import ISPyBDepositionNotMade
28
+ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
29
+ DataCollectionGridInfo,
30
+ DataCollectionInfo,
31
+ DataCollectionPositionInfo,
32
+ Orientation,
33
+ ScanDataInfo,
34
+ )
35
+ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
36
+ IspybIds,
37
+ StoreInIspyb,
38
+ )
39
+ from mx_bluesky.hyperion.log import ISPYB_LOGGER, set_dcgid_tag
40
+ from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
41
+ from mx_bluesky.hyperion.parameters.constants import CONST
42
+ from mx_bluesky.hyperion.parameters.gridscan import (
43
+ GridCommon,
44
+ )
45
+
46
+ if TYPE_CHECKING:
47
+ from event_model import Event, RunStart, RunStop
48
+
49
+
50
+ def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
51
+ return bpp.run_wrapper(
52
+ plan_generator,
53
+ md={
54
+ "activate_callbacks": ["GridscanISPyBCallback"],
55
+ "subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN,
56
+ "hyperion_parameters": parameters.model_dump_json(),
57
+ },
58
+ )
59
+
60
+
61
+ class GridscanISPyBCallback(BaseISPyBCallback):
62
+ """Callback class to handle the deposition of experiment parameters into the ISPyB
63
+ database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
64
+ recieving an 'event' document for the 'ispyb_reading_hardware' event, and updates the
65
+ deposition on recieving its final 'stop' document.
66
+
67
+ To use, subscribe the Bluesky RunEngine to an instance of this class.
68
+ E.g.:
69
+ ispyb_handler_callback = FGSISPyBCallback(parameters)
70
+ RE.subscribe(ispyb_handler_callback)
71
+ Or decorate a plan using bluesky.preprocessors.subs_decorator.
72
+
73
+ See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ emit: Callable[..., Any] | None = None,
80
+ ) -> None:
81
+ super().__init__(emit=emit)
82
+ self.ispyb: StoreInIspyb
83
+ self.ispyb_ids: IspybIds = IspybIds()
84
+ self._start_of_fgs_uid: str | None = None
85
+ self._processing_start_time: float | None = None
86
+
87
+ def activity_gated_start(self, doc: RunStart):
88
+ if doc.get("subplan_name") == CONST.PLAN.DO_FGS:
89
+ self._start_of_fgs_uid = doc.get("uid")
90
+ if doc.get("subplan_name") == CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN:
91
+ self.uid_to_finalize_on = doc.get("uid")
92
+ ISPYB_LOGGER.info(
93
+ "ISPyB callback received start document with experiment parameters and "
94
+ f"uid: {self.uid_to_finalize_on}"
95
+ )
96
+ self.params = GridCommon.from_json(doc.get("hyperion_parameters"))
97
+ self.ispyb = StoreInIspyb(self.ispyb_config)
98
+ data_collection_group_info = populate_data_collection_group(self.params)
99
+
100
+ scan_data_infos = [
101
+ ScanDataInfo(
102
+ data_collection_info=populate_remaining_data_collection_info(
103
+ None,
104
+ None,
105
+ populate_xy_data_collection_info(
106
+ self.params.detector_params,
107
+ ),
108
+ self.params,
109
+ ),
110
+ ),
111
+ ScanDataInfo(
112
+ data_collection_info=populate_remaining_data_collection_info(
113
+ None,
114
+ None,
115
+ populate_xz_data_collection_info(self.params.detector_params),
116
+ self.params,
117
+ )
118
+ ),
119
+ ]
120
+
121
+ self.ispyb_ids = self.ispyb.begin_deposition(
122
+ data_collection_group_info, scan_data_infos
123
+ )
124
+ set_dcgid_tag(self.ispyb_ids.data_collection_group_id)
125
+ return super().activity_gated_start(doc)
126
+
127
+ def activity_gated_event(self, doc: Event):
128
+ doc = super().activity_gated_event(doc)
129
+
130
+ descriptor_name = self.descriptors[doc["descriptor"]].get("name")
131
+ if descriptor_name == ZOCALO_READING_PLAN_NAME:
132
+ self._handle_zocalo_read_event(doc)
133
+ elif descriptor_name == CONST.DESCRIPTORS.OAV_GRID_SNAPSHOT_TRIGGERED:
134
+ scan_data_infos = self._handle_oav_grid_snapshot_triggered(doc)
135
+ self.ispyb_ids = self.ispyb.update_deposition(
136
+ self.ispyb_ids, scan_data_infos
137
+ )
138
+
139
+ return doc
140
+
141
+ def _handle_zocalo_read_event(self, doc):
142
+ crystal_summary = ""
143
+ if self._processing_start_time is not None:
144
+ proc_time = time() - self._processing_start_time
145
+ crystal_summary = f"Zocalo processing took {proc_time:.2f} s. "
146
+ bboxes: list[np.ndarray] = []
147
+ ISPYB_LOGGER.info(
148
+ f"Amending comment based on Zocalo reading doc: {format_doc_for_log(doc)}"
149
+ )
150
+ raw_results = doc["data"]["zocalo-results"]
151
+ if len(raw_results) > 0:
152
+ for n, res in enumerate(raw_results):
153
+ bb = res["bounding_box"]
154
+ diff = np.array(bb[1]) - np.array(bb[0])
155
+ bboxes.append(diff)
156
+
157
+ nicely_formatted_com = [
158
+ f"{np.round(com, 2)}" for com in res["centre_of_mass"]
159
+ ]
160
+ crystal_summary += (
161
+ f"Crystal {n + 1}: "
162
+ f"Strength {res['total_count']}; "
163
+ f"Position (grid boxes) {nicely_formatted_com}; "
164
+ f"Size (grid boxes) {bboxes[n]}; "
165
+ )
166
+ else:
167
+ crystal_summary += "Zocalo found no crystals in this gridscan."
168
+ assert (
169
+ self.ispyb_ids.data_collection_ids
170
+ ), "No data collection to add results to"
171
+ self.ispyb.append_to_comment(
172
+ self.ispyb_ids.data_collection_ids[0], crystal_summary
173
+ )
174
+
175
+ def _handle_oav_grid_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]:
176
+ assert self.ispyb_ids.data_collection_ids, "No current data collection"
177
+ assert self.params, "ISPyB handler didn't receive parameters!"
178
+ data = doc["data"]
179
+ data_collection_id = None
180
+ data_collection_info = DataCollectionInfo(
181
+ xtal_snapshot1=data.get("oav_grid_snapshot_last_path_full_overlay"),
182
+ xtal_snapshot2=data.get("oav_grid_snapshot_last_path_outer"),
183
+ xtal_snapshot3=data.get("oav_grid_snapshot_last_saved_path"),
184
+ n_images=(
185
+ data["oav_grid_snapshot_num_boxes_x"]
186
+ * data["oav_grid_snapshot_num_boxes_y"]
187
+ ),
188
+ )
189
+ microns_per_pixel_x = data["oav_grid_snapshot_microns_per_pixel_x"]
190
+ microns_per_pixel_y = data["oav_grid_snapshot_microns_per_pixel_y"]
191
+ data_collection_grid_info = DataCollectionGridInfo(
192
+ dx_in_mm=data["oav_grid_snapshot_box_width"] * microns_per_pixel_x / 1000,
193
+ dy_in_mm=data["oav_grid_snapshot_box_width"] * microns_per_pixel_y / 1000,
194
+ steps_x=data["oav_grid_snapshot_num_boxes_x"],
195
+ steps_y=data["oav_grid_snapshot_num_boxes_y"],
196
+ microns_per_pixel_x=microns_per_pixel_x,
197
+ microns_per_pixel_y=microns_per_pixel_y,
198
+ snapshot_offset_x_pixel=int(data["oav_grid_snapshot_top_left_x"]),
199
+ snapshot_offset_y_pixel=int(data["oav_grid_snapshot_top_left_y"]),
200
+ orientation=Orientation.HORIZONTAL,
201
+ snaked=True,
202
+ )
203
+ data_collection_info.comments = construct_comment_for_gridscan(
204
+ data_collection_grid_info
205
+ )
206
+ if len(self.ispyb_ids.data_collection_ids) > self._oav_snapshot_event_idx:
207
+ data_collection_id = self.ispyb_ids.data_collection_ids[
208
+ self._oav_snapshot_event_idx
209
+ ]
210
+ self._populate_axis_info(data_collection_info, doc["data"]["smargon-omega"])
211
+
212
+ scan_data_info = ScanDataInfo(
213
+ data_collection_info=data_collection_info,
214
+ data_collection_id=data_collection_id,
215
+ data_collection_grid_info=data_collection_grid_info,
216
+ )
217
+ ISPYB_LOGGER.info("Updating ispyb data collection after oav snapshot.")
218
+ self._oav_snapshot_event_idx += 1
219
+ return [scan_data_info]
220
+
221
+ def _populate_axis_info(
222
+ self, data_collection_info: DataCollectionInfo, omega_start: float | None
223
+ ):
224
+ if omega_start is not None:
225
+ omega_in_gda_space = -omega_start
226
+ data_collection_info.omega_start = omega_in_gda_space
227
+ data_collection_info.axis_start = omega_in_gda_space
228
+ data_collection_info.axis_end = omega_in_gda_space
229
+ data_collection_info.axis_range = 0
230
+
231
+ def populate_info_for_update(
232
+ self,
233
+ event_sourced_data_collection_info: DataCollectionInfo,
234
+ event_sourced_position_info: DataCollectionPositionInfo | None,
235
+ params: DiffractionExperimentWithSample,
236
+ ) -> Sequence[ScanDataInfo]:
237
+ assert (
238
+ self.ispyb_ids.data_collection_ids
239
+ ), "Expect at least one valid data collection to record scan data"
240
+ xy_scan_data_info = ScanDataInfo(
241
+ data_collection_info=event_sourced_data_collection_info,
242
+ data_collection_id=self.ispyb_ids.data_collection_ids[0],
243
+ )
244
+ scan_data_infos = [xy_scan_data_info]
245
+
246
+ data_collection_id = (
247
+ self.ispyb_ids.data_collection_ids[1]
248
+ if len(self.ispyb_ids.data_collection_ids) > 1
249
+ else None
250
+ )
251
+ xz_scan_data_info = ScanDataInfo(
252
+ data_collection_info=event_sourced_data_collection_info,
253
+ data_collection_id=data_collection_id,
254
+ )
255
+ scan_data_infos.append(xz_scan_data_info)
256
+ return scan_data_infos
257
+
258
+ def activity_gated_stop(self, doc: RunStop) -> RunStop:
259
+ if doc.get("run_start") == self._start_of_fgs_uid:
260
+ self._processing_start_time = time()
261
+ if doc.get("run_start") == self.uid_to_finalize_on:
262
+ ISPYB_LOGGER.info(
263
+ "ISPyB callback received stop document corresponding to start document "
264
+ f"with uid: {self.uid_to_finalize_on}."
265
+ )
266
+ if self.ispyb_ids == IspybIds():
267
+ raise ISPyBDepositionNotMade("ispyb was not initialised at run start")
268
+ return super().activity_gated_stop(doc)
269
+ return self._tag_doc(doc)
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy
4
+ from dodal.devices.detector import DetectorParams
5
+ from dodal.devices.oav import utils as oav_utils
6
+
7
+ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
8
+ DataCollectionGridInfo,
9
+ DataCollectionInfo,
10
+ )
11
+
12
+
13
+ def populate_xz_data_collection_info(detector_params: DetectorParams):
14
+ assert (
15
+ detector_params.omega_start is not None
16
+ and detector_params.run_number is not None
17
+ ), "StoreGridscanInIspyb failed to get parameters"
18
+ run_number = detector_params.run_number + 1
19
+ info = DataCollectionInfo(
20
+ data_collection_number=run_number,
21
+ )
22
+ return info
23
+
24
+
25
+ def populate_xy_data_collection_info(detector_params: DetectorParams):
26
+ return DataCollectionInfo(
27
+ data_collection_number=detector_params.run_number,
28
+ )
29
+
30
+
31
+ def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
32
+ assert grid_info is not None, "StoreGridScanInIspyb failed to get parameters"
33
+
34
+ bottom_right = oav_utils.bottom_right_from_top_left(
35
+ numpy.array(
36
+ [grid_info.snapshot_offset_x_pixel, grid_info.snapshot_offset_y_pixel]
37
+ ), # type: ignore
38
+ grid_info.steps_x,
39
+ grid_info.steps_y,
40
+ grid_info.dx_in_mm,
41
+ grid_info.dy_in_mm,
42
+ grid_info.microns_per_pixel_x,
43
+ grid_info.microns_per_pixel_y,
44
+ )
45
+ return (
46
+ "Hyperion: Xray centring - Diffraction grid scan of "
47
+ f"{grid_info.steps_x} by "
48
+ f"{grid_info.steps_y} images in "
49
+ f"{(grid_info.dx_in_mm * 1e3):.1f} um by "
50
+ f"{(grid_info.dy_in_mm * 1e3):.1f} um steps. "
51
+ f"Top left (px): [{int(grid_info.snapshot_offset_x_pixel)},{int(grid_info.snapshot_offset_y_pixel)}], "
52
+ f"bottom right (px): [{bottom_right[0]},{bottom_right[1]}]."
53
+ )