dls-dodal 1.58.0__py3-none-any.whl → 1.60.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 (71) hide show
  1. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/METADATA +3 -3
  2. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/RECORD +71 -47
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/__init__.py +1 -0
  5. dodal/beamlines/b07.py +10 -5
  6. dodal/beamlines/b07_1.py +10 -5
  7. dodal/beamlines/b21.py +22 -0
  8. dodal/beamlines/i02_1.py +80 -0
  9. dodal/beamlines/i03.py +5 -3
  10. dodal/beamlines/i04.py +5 -3
  11. dodal/beamlines/i09.py +10 -9
  12. dodal/beamlines/i09_1.py +10 -5
  13. dodal/beamlines/i10-1.py +25 -0
  14. dodal/beamlines/i10.py +17 -1
  15. dodal/beamlines/i11.py +0 -17
  16. dodal/beamlines/i15.py +242 -0
  17. dodal/beamlines/i15_1.py +156 -0
  18. dodal/beamlines/i19_1.py +3 -1
  19. dodal/beamlines/i19_2.py +12 -1
  20. dodal/beamlines/i21.py +27 -0
  21. dodal/beamlines/i22.py +12 -2
  22. dodal/beamlines/i24.py +32 -3
  23. dodal/beamlines/k07.py +31 -0
  24. dodal/beamlines/p60.py +10 -9
  25. dodal/common/watcher_utils.py +1 -1
  26. dodal/devices/apple2_undulator.py +18 -142
  27. dodal/devices/attenuator/attenuator.py +48 -2
  28. dodal/devices/attenuator/filter.py +3 -0
  29. dodal/devices/attenuator/filter_selections.py +26 -0
  30. dodal/devices/eiger.py +2 -1
  31. dodal/devices/electron_analyser/__init__.py +4 -0
  32. dodal/devices/electron_analyser/abstract/base_driver_io.py +30 -18
  33. dodal/devices/electron_analyser/energy_sources.py +101 -0
  34. dodal/devices/electron_analyser/specs/detector.py +6 -6
  35. dodal/devices/electron_analyser/specs/driver_io.py +7 -15
  36. dodal/devices/electron_analyser/vgscienta/detector.py +6 -6
  37. dodal/devices/electron_analyser/vgscienta/driver_io.py +7 -14
  38. dodal/devices/fast_grid_scan.py +130 -64
  39. dodal/devices/focusing_mirror.py +30 -0
  40. dodal/devices/i02_1/__init__.py +0 -0
  41. dodal/devices/i02_1/fast_grid_scan.py +61 -0
  42. dodal/devices/i02_1/sample_motors.py +19 -0
  43. dodal/devices/i04/murko_results.py +69 -23
  44. dodal/devices/i10/i10_apple2.py +282 -140
  45. dodal/devices/i15/dcm.py +77 -0
  46. dodal/devices/i15/focussing_mirror.py +71 -0
  47. dodal/devices/i15/jack.py +39 -0
  48. dodal/devices/i15/laue.py +18 -0
  49. dodal/devices/i15/motors.py +27 -0
  50. dodal/devices/i15/multilayer_mirror.py +25 -0
  51. dodal/devices/i15/rail.py +17 -0
  52. dodal/devices/i21/__init__.py +3 -0
  53. dodal/devices/i21/enums.py +8 -0
  54. dodal/devices/i22/nxsas.py +2 -0
  55. dodal/devices/i24/commissioning_jungfrau.py +114 -0
  56. dodal/devices/motors.py +52 -1
  57. dodal/devices/slits.py +18 -0
  58. dodal/devices/smargon.py +0 -56
  59. dodal/devices/temperture_controller/__init__.py +3 -0
  60. dodal/devices/temperture_controller/lakeshore/__init__.py +0 -0
  61. dodal/devices/temperture_controller/lakeshore/lakeshore.py +204 -0
  62. dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +112 -0
  63. dodal/devices/tetramm.py +38 -16
  64. dodal/devices/v2f.py +39 -0
  65. dodal/devices/zebra/zebra.py +1 -0
  66. dodal/devices/zebra/zebra_constants_mapping.py +1 -1
  67. dodal/parameters/experiment_parameter_base.py +1 -5
  68. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/WHEEL +0 -0
  69. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/entry_points.txt +0 -0
  70. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/licenses/LICENSE +0 -0
  71. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
1
+ from ophyd_async.core import SignalR, derived_signal_r, soft_signal_r_and_setter
2
+ from ophyd_async.epics.core import epics_signal_rw_rbv
3
+
4
+ from dodal.devices.fast_grid_scan import (
5
+ FastGridScanCommon,
6
+ GridScanParamsCommon,
7
+ MotionProgram,
8
+ WithDwellTime,
9
+ )
10
+ from dodal.log import LOGGER
11
+
12
+
13
+ class ZebraGridScanParamsTwoD(GridScanParamsCommon, WithDwellTime):
14
+ """
15
+ Params for 2D Zebra FGS. Adds on the dwell time, which is really the time
16
+ between trigger positions.
17
+ """
18
+
19
+
20
+ class ZebraFastGridScanTwoD(FastGridScanCommon[ZebraGridScanParamsTwoD]):
21
+ """i02-1's EPICS interface for the 2D FGS differs slightly from the standard 3D
22
+ version:
23
+ - No Z steps, Z step sizes, or Y2 start positions, or Z2 start
24
+ - No scan valid PV - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
25
+ - No program_number - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
26
+ """
27
+
28
+ def __init__(
29
+ self, prefix: str, motion_controller_prefix: str, name: str = ""
30
+ ) -> None:
31
+ full_prefix = prefix + "FGS:"
32
+ super().__init__(full_prefix, motion_controller_prefix, name)
33
+
34
+ # This signal could be put in the common device if the prefix gets standardised.
35
+ # See https://github.com/DiamondLightSource/mx-bluesky/issues/1203
36
+ self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}EXPOSURE_TIME")
37
+
38
+ self.movable_params["dwell_time_ms"] = self.dwell_time_ms
39
+
40
+ def _create_expected_images_signal(self):
41
+ return derived_signal_r(
42
+ self._calculate_expected_images,
43
+ x=self.x_steps,
44
+ y=self.y_steps,
45
+ )
46
+
47
+ def _calculate_expected_images(self, x: int, y: int) -> int:
48
+ LOGGER.info(f"Reading num of images found {x, y} images in each axis")
49
+ return x * y
50
+
51
+ # VMXm triggers the grid scan through GDA, which has its own validity check
52
+ # so whilst this PV is being added, it isn't essential
53
+ def _create_scan_invalid_signal(self, prefix: str) -> SignalR[float]:
54
+ return soft_signal_r_and_setter(float, 0)[0]
55
+
56
+ def _create_motion_program(self, motion_controller_prefix):
57
+ return MotionProgram(motion_controller_prefix, has_prog_num=False)
58
+
59
+ # To be standardised in https://github.com/DiamondLightSource/mx-bluesky/issues/1203
60
+ def _create_position_counter(self, prefix: str):
61
+ return epics_signal_rw_rbv(int, f"{prefix}POS_COUNTER")
@@ -0,0 +1,19 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
+
4
+
5
+ class SampleMotors(StandardReadable):
6
+ """Virtual Smaract motors on i02-1 (VMXm)"""
7
+
8
+ def __init__(
9
+ self,
10
+ prefix: str,
11
+ name: str = "",
12
+ ):
13
+ # See https://github.com/DiamondLightSource/mx-bluesky/issues/1212
14
+ # regarding a potential motion issue with omega
15
+ with self.add_children_as_readables():
16
+ self.x = Motor(f"{prefix}X")
17
+ self.z = Motor(f"{prefix}Z")
18
+ self.omega = Motor(f"{prefix}OMEGA")
19
+ super().__init__(name=name)
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import pickle
3
+ from dataclasses import dataclass
3
4
  from enum import Enum
4
5
  from typing import TypedDict
5
6
 
@@ -21,9 +22,6 @@ from dodal.log import LOGGER
21
22
 
22
23
  NO_MURKO_RESULT = (-1, -1)
23
24
 
24
- MurkoResult = dict
25
- FullMurkoResults = dict[str, list[MurkoResult]]
26
-
27
25
 
28
26
  class MurkoMetadata(TypedDict):
29
27
  zoom_percentage: float
@@ -42,6 +40,19 @@ class Coord(Enum):
42
40
  z = 2
43
41
 
44
42
 
43
+ @dataclass
44
+ class MurkoResult:
45
+ centre_px: tuple
46
+ x_dist_mm: float
47
+ y_dist_mm: float
48
+ omega: float
49
+ uuid: str
50
+
51
+
52
+ class NoResultsFound(ValueError):
53
+ pass
54
+
55
+
45
56
  class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
46
57
  """Device that takes crystal centre values from Murko and uses them to set the
47
58
  x, y, z coordinate of the sample to be in line with the beam centre.
@@ -59,6 +70,8 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
59
70
  """
60
71
 
61
72
  TIMEOUT_S = 2
73
+ PERCENTAGE_TO_USE = 25
74
+ NUMBER_OF_WRONG_RESULTS_TO_LOG = 5
62
75
 
63
76
  def __init__(
64
77
  self,
@@ -74,12 +87,10 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
74
87
  db=redis_db,
75
88
  )
76
89
  self.pubsub = self.redis_client.pubsub()
77
- self._last_omega = 0
78
90
  self.sample_id = soft_signal_rw(str) # Should get from redis
79
91
  self.stop_angle = stop_angle
80
- self.x_dists_mm = []
81
- self.y_dists_mm = []
82
- self.omegas = []
92
+
93
+ self._reset()
83
94
 
84
95
  with self.add_children_as_readables():
85
96
  # Diffs from current x/y/z
@@ -88,6 +99,10 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
88
99
  self.z_mm, self._z_mm_setter = soft_signal_r_and_setter(float)
89
100
  super().__init__(name=name)
90
101
 
102
+ def _reset(self):
103
+ self._last_omega = 0
104
+ self.results: list[MurkoResult] = []
105
+
91
106
  @AsyncStatus.wrap
92
107
  async def stage(self):
93
108
  await self.pubsub.subscribe("murko-results")
@@ -97,6 +112,7 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
97
112
 
98
113
  @AsyncStatus.wrap
99
114
  async def unstage(self):
115
+ self._reset()
100
116
  await self.pubsub.unsubscribe()
101
117
 
102
118
  @AsyncStatus.wrap
@@ -106,19 +122,26 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
106
122
  while self._last_omega < self.stop_angle:
107
123
  # waits here for next batch to be received
108
124
  message = await self.pubsub.get_message(timeout=self.TIMEOUT_S)
109
- if message is None: # No more messages to process
110
- break
125
+ if message is None:
126
+ continue
111
127
  await self.process_batch(message, sample_id)
112
128
 
113
- for i in range(len(self.omegas)):
114
- LOGGER.debug(
115
- f"omega: {round(self.omegas[i], 2)}, x: {round(self.x_dists_mm[i], 2)}, y: {round(self.y_dists_mm[i], 2)}"
116
- )
129
+ if not self.results:
130
+ raise NoResultsFound("No results retrieved from Murko")
131
+
132
+ for result in self.results:
133
+ LOGGER.debug(result)
134
+
135
+ self.filter_outliers()
117
136
 
118
- LOGGER.info(f"Using average of x beam distances: {self.x_dists_mm}")
119
- avg_x = float(np.mean(self.x_dists_mm))
120
- LOGGER.info(f"Finding least square y and z from y distances: {self.y_dists_mm}")
121
- best_y, best_z = get_yz_least_squares(self.y_dists_mm, self.omegas)
137
+ x_dists_mm = [result.x_dist_mm for result in self.results]
138
+ y_dists_mm = [result.y_dist_mm for result in self.results]
139
+ omegas = [result.omega for result in self.results]
140
+
141
+ LOGGER.info(f"Using average of x beam distances: {x_dists_mm}")
142
+ avg_x = float(np.mean(x_dists_mm))
143
+ LOGGER.info(f"Finding least square y and z from y distances: {y_dists_mm}")
144
+ best_y, best_z = get_yz_least_squares(y_dists_mm, omegas)
122
145
  # x, y, z are relative to beam centre. Need to move negative these values to get centred.
123
146
  self._x_mm_setter(-avg_x)
124
147
  self._y_mm_setter(-best_y)
@@ -163,15 +186,38 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
163
186
  centre_px[0],
164
187
  centre_px[1],
165
188
  )
166
- self.x_dists_mm.append(
167
- beam_dist_px[0] * metadata["microns_per_x_pixel"] / 1000
168
- )
169
- self.y_dists_mm.append(
170
- beam_dist_px[1] * metadata["microns_per_y_pixel"] / 1000
189
+ self.results.append(
190
+ MurkoResult(
191
+ centre_px=centre_px,
192
+ x_dist_mm=beam_dist_px[0] * metadata["microns_per_x_pixel"] / 1000,
193
+ y_dist_mm=beam_dist_px[1] * metadata["microns_per_y_pixel"] / 1000,
194
+ omega=omega,
195
+ uuid=metadata["uuid"],
196
+ )
171
197
  )
172
- self.omegas.append(omega)
173
198
  self._last_omega = omega
174
199
 
200
+ def filter_outliers(self):
201
+ """Whilst murko is not fully trained it often gives us poor results.
202
+ When it is wrong it usually picks up the base of the pin, rather than the tip,
203
+ meaning that by keeping only a percentage of the results with the smallest X we
204
+ remove many of the outliers.
205
+ """
206
+ LOGGER.info(f"Number of results before filtering: {len(self.results)}")
207
+ sorted_results = sorted(self.results, key=lambda item: item.centre_px[0])
208
+
209
+ worst_results = [
210
+ r.uuid for r in sorted_results[-self.NUMBER_OF_WRONG_RESULTS_TO_LOG :]
211
+ ]
212
+
213
+ LOGGER.info(
214
+ f"Worst {self.NUMBER_OF_WRONG_RESULTS_TO_LOG} murko results were {worst_results}"
215
+ )
216
+ cutoff = max(1, int(len(sorted_results) * self.PERCENTAGE_TO_USE / 100))
217
+ smallest_x = sorted_results[:cutoff]
218
+ self.results = smallest_x
219
+ LOGGER.info(f"Number of results after filtering: {len(self.results)}")
220
+
175
221
 
176
222
  def get_yz_least_squares(vertical_dists: list, omegas: list) -> tuple[float, float]:
177
223
  """Get the least squares solution for y and z from the vertical distances and omega angles.