dls-dodal 1.36.3__py3-none-any.whl → 1.38.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 (58) hide show
  1. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/METADATA +4 -3
  2. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/RECORD +58 -38
  3. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/i02_1.py +37 -0
  6. dodal/beamlines/i03.py +34 -5
  7. dodal/beamlines/i04.py +16 -5
  8. dodal/beamlines/i10.py +105 -0
  9. dodal/beamlines/i13_1.py +20 -2
  10. dodal/beamlines/i22.py +15 -0
  11. dodal/beamlines/i24.py +14 -2
  12. dodal/beamlines/p99.py +6 -2
  13. dodal/beamlines/training_rig.py +10 -1
  14. dodal/common/crystal_metadata.py +3 -3
  15. dodal/common/udc_directory_provider.py +3 -1
  16. dodal/devices/aperturescatterguard.py +3 -0
  17. dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
  18. dodal/devices/attenuator/filter.py +11 -0
  19. dodal/devices/attenuator/filter_selections.py +72 -0
  20. dodal/devices/bimorph_mirror.py +151 -0
  21. dodal/devices/current_amplifiers/__init__.py +34 -0
  22. dodal/devices/current_amplifiers/current_amplifier.py +103 -0
  23. dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
  24. dodal/devices/current_amplifiers/femto.py +143 -0
  25. dodal/devices/current_amplifiers/sr570.py +214 -0
  26. dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
  27. dodal/devices/detector/det_dim_constants.py +15 -0
  28. dodal/devices/eiger_odin.py +3 -3
  29. dodal/devices/fast_grid_scan.py +8 -3
  30. dodal/devices/flux.py +10 -3
  31. dodal/devices/i03/beamstop.py +85 -0
  32. dodal/devices/i04/transfocator.py +67 -53
  33. dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
  34. dodal/devices/i10/rasor/rasor_motors.py +62 -0
  35. dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
  36. dodal/devices/i13_1/__init__.py +0 -0
  37. dodal/devices/i13_1/merlin.py +33 -0
  38. dodal/devices/i13_1/merlin_controller.py +52 -0
  39. dodal/devices/i13_1/merlin_io.py +17 -0
  40. dodal/devices/i24/beam_center.py +1 -1
  41. dodal/devices/p45.py +31 -20
  42. dodal/devices/p99/sample_stage.py +2 -28
  43. dodal/devices/robot.py +2 -2
  44. dodal/devices/s4_slit_gaps.py +8 -4
  45. dodal/devices/undulator_dcm.py +9 -11
  46. dodal/devices/util/lookup_tables.py +14 -10
  47. dodal/devices/zebra/__init__.py +0 -0
  48. dodal/devices/{zebra.py → zebra/zebra.py} +9 -33
  49. dodal/devices/zebra/zebra_constants_mapping.py +96 -0
  50. dodal/devices/zocalo/zocalo_interaction.py +2 -1
  51. dodal/devices/zocalo/zocalo_results.py +22 -2
  52. dodal/log.py +2 -2
  53. dodal/plans/wrapped.py +3 -3
  54. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/LICENSE +0 -0
  55. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/entry_points.txt +0 -0
  56. {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/top_level.txt +0 -0
  57. /dodal/devices/{zebra_controlled_shutter.py → zebra/zebra_controlled_shutter.py} +0 -0
  58. /dodal/{devices/util → plans}/save_panda.py +0 -0
@@ -14,37 +14,7 @@ from ophyd_async.core import (
14
14
  )
15
15
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
16
16
 
17
- # These constants refer to I03's Zebra. See https://github.com/DiamondLightSource/dodal/issues/772
18
- # Sources
19
- DISCONNECT = 0
20
- IN1_TTL = 1
21
- IN2_TTL = 4
22
- IN3_TTL = 7
23
- IN4_TTL = 10
24
- PC_ARM = 29
25
- PC_GATE = 30
26
- PC_PULSE = 31
27
- AND3 = 34
28
- AND4 = 35
29
- OR1 = 36
30
- PULSE1 = 52
31
- PULSE2 = 53
32
- SOFT_IN1 = 60
33
- SOFT_IN2 = 61
34
- SOFT_IN3 = 62
35
-
36
- # Instrument specific
37
- TTL_DETECTOR = 1
38
- TTL_SHUTTER = 2
39
- TTL_XSPRESS3 = 3
40
- TTL_PANDA = 4
41
-
42
- # The AND gate that controls the automatic shutter
43
- AUTO_SHUTTER_GATE = 2
44
-
45
- # The first two inputs of the auto shutter gate.
46
- AUTO_SHUTTER_INPUT_1 = 1
47
- AUTO_SHUTTER_INPUT_2 = 2
17
+ from dodal.devices.zebra.zebra_constants_mapping import ZebraMapping
48
18
 
49
19
 
50
20
  class ArmSource(StrictEnum):
@@ -81,6 +51,11 @@ class I24Axes:
81
51
 
82
52
 
83
53
  class RotationDirection(StrictEnum):
54
+ """
55
+ Defines for a swept angle whether the scan width (sweep) is to be added or subtracted from
56
+ the initial angle to obtain the final angle.
57
+ """
58
+
84
59
  POSITIVE = "Positive"
85
60
  NEGATIVE = "Negative"
86
61
 
@@ -281,7 +256,7 @@ class LogicGateConfiguration:
281
256
  for input, (source, invert) in enumerate(
282
257
  zip(self.sources, self.invert, strict=False)
283
258
  ):
284
- input_strings.append(f"INP{input+1}={'!' if invert else ''}{source}")
259
+ input_strings.append(f"INP{input + 1}={'!' if invert else ''}{source}")
285
260
 
286
261
  return ", ".join(input_strings)
287
262
 
@@ -298,7 +273,8 @@ class SoftInputs(StandardReadable):
298
273
  class Zebra(StandardReadable):
299
274
  """The Zebra device."""
300
275
 
301
- def __init__(self, name: str, prefix: str) -> None:
276
+ def __init__(self, mapping: ZebraMapping, name: str, prefix: str) -> None:
277
+ self.mapping = mapping
302
278
  self.pc = PositionCompare(prefix, name)
303
279
  self.output = ZebraOutputPanel(prefix, name)
304
280
  self.inputs = SoftInputs(prefix, name)
@@ -0,0 +1,96 @@
1
+ from collections import Counter
2
+
3
+ from pydantic import BaseModel, Field, model_validator
4
+
5
+
6
+ class ZebraMappingValidations(BaseModel):
7
+ """Raises an exception if field set to -1 is accessed, and validate against
8
+ multiple fields mapping to the same integer"""
9
+
10
+ def __getattribute__(self, name: str):
11
+ """To protect against mismatch between the Zebra configuration that a plan expects and the Zebra which has
12
+ been instantiated, raise exception if a field which has been set to -1 is accessed."""
13
+ value = object.__getattribute__(self, name)
14
+ if not name.startswith("__"):
15
+ if value == -1:
16
+ raise UnmappedZebraException(
17
+ f"'{type(self).__name__}.{name}' was accessed but is set to -1. Please check the zebra mappings against the zebra's physical configuration"
18
+ )
19
+ return value
20
+
21
+ @model_validator(mode="after")
22
+ def ensure_no_duplicate_connections(self):
23
+ """Check that TTL outputs and sources are mapped to unique integers"""
24
+
25
+ integer_fields = {
26
+ key: value
27
+ for key, value in self.model_dump().items()
28
+ if isinstance(value, int) and value != -1
29
+ }
30
+ counted_vals = Counter(integer_fields.values())
31
+ integer_fields_with_duplicates = {
32
+ k: v for k, v in integer_fields.items() if counted_vals[v] > 1
33
+ }
34
+ if len(integer_fields_with_duplicates):
35
+ raise ValueError(
36
+ f"Each field in {type(self)} must be mapped to a unique integer. Duplicate fields: {integer_fields_with_duplicates}"
37
+ )
38
+ return self
39
+
40
+
41
+ class ZebraTTLOutputs(ZebraMappingValidations):
42
+ """Maps hardware to the Zebra TTL output (1-4) that they're physically wired to, or
43
+ None if that hardware is not connected. A value of -1 means this hardware is not connected."""
44
+
45
+ TTL_EIGER: int = Field(default=-1, ge=-1, le=4)
46
+ TTL_PILATUS: int = Field(default=-1, ge=-1, le=4)
47
+ TTL_FAST_SHUTTER: int = Field(default=-1, ge=-1, le=4)
48
+ TTL_DETECTOR: int = Field(default=-1, ge=-1, le=4)
49
+ TTL_SHUTTER: int = Field(default=-1, ge=-1, le=4)
50
+ TTL_XSPRESS3: int = Field(default=-1, ge=-1, le=4)
51
+ TTL_PANDA: int = Field(default=-1, ge=-1, le=4)
52
+
53
+
54
+ class ZebraSources(ZebraMappingValidations):
55
+ """Maps internal Zebra signal source to their integer PV value"""
56
+
57
+ DISCONNECT: int = Field(default=0, ge=0, le=63)
58
+ IN1_TTL: int = Field(default=1, ge=0, le=63)
59
+ IN2_TTL: int = Field(default=63, ge=0, le=63)
60
+ IN3_TTL: int = Field(default=7, ge=0, le=63)
61
+ IN4_TTL: int = Field(default=10, ge=0, le=63)
62
+ PC_ARM: int = Field(default=29, ge=0, le=63)
63
+ PC_GATE: int = Field(default=30, ge=0, le=63)
64
+ PC_PULSE: int = Field(default=31, ge=0, le=63)
65
+ AND3: int = Field(default=34, ge=0, le=63)
66
+ AND4: int = Field(default=35, ge=0, le=63)
67
+ OR1: int = Field(default=36, ge=0, le=63)
68
+ PULSE1: int = Field(default=52, ge=0, le=63)
69
+ PULSE2: int = Field(default=53, ge=0, le=63)
70
+ SOFT_IN1: int = Field(default=60, ge=0, le=63)
71
+ SOFT_IN2: int = Field(default=61, ge=0, le=63)
72
+ SOFT_IN3: int = Field(default=62, ge=0, le=63)
73
+
74
+
75
+ class ZebraMapping(ZebraMappingValidations):
76
+ """Mappings to locate a Zebra device's Ophyd signals based on a specific
77
+ Zebra's hardware configuration and wiring.
78
+ """
79
+
80
+ # Zebra ophyd signal for connection can be accessed
81
+ # with, eg, zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR]
82
+ outputs: ZebraTTLOutputs = ZebraTTLOutputs()
83
+
84
+ # Zebra ophyd signal sources can be mapped to a zebra output by doing, eg,
85
+ # bps.abs_set(zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
86
+ # zebra.mapping.sources.AND3)
87
+ sources: ZebraSources = ZebraSources()
88
+
89
+ # Which of the Zebra's four AND gates is used to control the automatic shutter, if it's being used.
90
+ # After defining, the correct GateControl device can be accessed with, eg,
91
+ # zebra.logic_gates.and_gates[zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER]. Set to -1 if not being used.
92
+ AND_GATE_FOR_AUTO_SHUTTER: int = Field(default=-1, ge=-1, le=4)
93
+
94
+
95
+ class UnmappedZebraException(Exception):
96
+ pass
@@ -55,7 +55,8 @@ class ZocaloTrigger:
55
55
  intended to be used in bluesky callback classes. To get results from zocalo back
56
56
  into a plan, use the ZocaloResults ophyd device.
57
57
 
58
- see https://github.com/DiamondLightSource/dodal/wiki/How-to-Interact-with-Zocalo"""
58
+ see https://diamondlightsource.github.io/dodal/main/how-to/zocalo.html for
59
+ more information about zocalo."""
59
60
 
60
61
  def __init__(self, environment: str = ZOCALO_ENV):
61
62
  self.zocalo_environment: str = environment
@@ -11,7 +11,7 @@ import workflows.recipe
11
11
  import workflows.transport
12
12
  from bluesky.protocols import Triggerable
13
13
  from bluesky.utils import Msg
14
- from deepdiff import DeepDiff
14
+ from deepdiff.diff import DeepDiff
15
15
  from ophyd_async.core import (
16
16
  Array1D,
17
17
  AsyncStatus,
@@ -53,6 +53,26 @@ ZOCALO_STAGE_GROUP = "clear zocalo queue"
53
53
 
54
54
 
55
55
  class XrcResult(TypedDict):
56
+ """
57
+ Information about a diffracting centre.
58
+
59
+ NOTE: the coordinate systems of centre_of_mass and max_voxel/bounding_box are not
60
+ the same; centre_of_mass coordinates are continuous whereas max_voxel and bounding_box
61
+ coordinates are discrete.
62
+ Attributes:
63
+ centre_of_mass: The position of the centre of mass of the crystal, adjusted so that
64
+ grid box centres lie on integer grid coordinates, such that a 1x1x1 crystal detected in
65
+ a single grid box at 0, 0, 0, has c.o.m. of 0, 0, 0, not 0.5, 0.5, 0.5
66
+ max_voxel: Position of the voxel with the maximum count, in integer coordinates
67
+ max_count: max count achieved in a single voxel for the crystal
68
+ n_voxels: Number of voxels (aka grid boxes) in the diffracting centre
69
+ total_count: Total of above-threshold spot counts in the labelled voxels
70
+ bounding_box: The rectangular prism that bounds the crystal, expressed
71
+ as the volume of whole boxes as a half-open range i.e such that
72
+ p1 = (x1, y1, z1) <= p < p2 = (x2, y2, z2) and
73
+ p2 - p1 gives the dimensions in whole voxels.
74
+ """
75
+
56
76
  centre_of_mass: list[float]
57
77
  max_voxel: list[int]
58
78
  max_count: int
@@ -133,7 +153,7 @@ class ZocaloResults(StandardReadable, Triggerable):
133
153
  self.use_cpu_and_gpu = use_cpu_and_gpu
134
154
 
135
155
  self.centre_of_mass, self._com_setter = soft_signal_r_and_setter(
136
- Array1D[np.uint64], name="centre_of_mass"
156
+ Array1D[np.float64], name="centre_of_mass"
137
157
  )
138
158
  self.bounding_box, self._bounding_box_setter = soft_signal_r_and_setter(
139
159
  Array1D[np.uint64], name="bounding_box"
dodal/log.py CHANGED
@@ -152,7 +152,7 @@ def set_up_graylog_handler(logger: Logger, host: str, port: int):
152
152
  def set_up_INFO_file_handler(logger, path: Path, filename: str):
153
153
  """Set up a file handler for the logger, at INFO level, which will keep 30 days
154
154
  of logs, rotating once per day. Creates the directory if necessary."""
155
- print(f"Logging to INFO file handler {path/filename}")
155
+ print(f"Logging to INFO file handler {path / filename}")
156
156
  path.mkdir(parents=True, exist_ok=True)
157
157
  file_handler = TimedRotatingFileHandler(
158
158
  filename=path / filename, when="MIDNIGHT", backupCount=INFO_LOG_DAYS
@@ -169,7 +169,7 @@ def set_up_DEBUG_memory_handler(
169
169
  log file when it sees a message of severity ERROR. Creates the directory if
170
170
  necessary"""
171
171
  debug_path = path / "debug"
172
- print(f"Logging to DEBUG handler {debug_path/filename}")
172
+ print(f"Logging to DEBUG handler {debug_path / filename}")
173
173
  debug_path.mkdir(parents=True, exist_ok=True)
174
174
  file_handler = TimedRotatingFileHandler(
175
175
  filename=debug_path / filename, when="H", backupCount=DEBUG_LOG_FILES_TO_KEEP
dodal/plans/wrapped.py CHANGED
@@ -49,9 +49,9 @@ def count(
49
49
  Wraps bluesky.plans.count(det, num, delay, md=metadata) exposing only serializable
50
50
  parameters and metadata."""
51
51
  if isinstance(delay, Sequence):
52
- assert (
53
- len(delay) == num - 1
54
- ), f"Number of delays given must be {num - 1}: was given {len(delay)}"
52
+ assert len(delay) == num - 1, (
53
+ f"Number of delays given must be {num - 1}: was given {len(delay)}"
54
+ )
55
55
  metadata = metadata or {}
56
56
  metadata["shape"] = (num,)
57
57
  yield from bp.count(tuple(detectors), num, delay=delay, md=metadata)
File without changes