diff --git a/docs/conf.py b/docs/conf.py
index c66bf0b8ee..4cd5e51b68 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -86,6 +86,10 @@
extensions.append("sphinx.ext.napoleon")
+napoleon_custom_sections = [
+ ("Emits", "params_style"),
+]
+
napoleon_numpy_docstring = False
@@ -99,7 +103,11 @@
extensions.append("myst_parser")
-myst_enable_extensions = ("fieldlist",)
+myst_enable_extensions = (
+ "attrs_block",
+ "colon_fence",
+ "fieldlist",
+)
# -- Options for sphinxcontrib.mermaid
diff --git a/docs/source/dev/docs.md b/docs/source/dev/docs.md
index 41d34ccdf9..9fe52f2cdf 100644
--- a/docs/source/dev/docs.md
+++ b/docs/source/dev/docs.md
@@ -84,6 +84,27 @@ extension is enabled to handle docstrings within the Python code
and it is configured for
[Google-style docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings).
+#### Custom sections for docstrings
+
+A
+[custom section](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#confval-napoleon_custom_sections)
+title for docstrings is available to document *hardware objects*:
+
+* `Emits` for signals emitted by *hardware objects*
+
+It can be used like in this example:
+
+```python
+class Thing(HardwareObject):
+ """Some thing.
+
+ Emits:
+ isReady (bool):
+ Emitted when the readiness state changes.
+ """
+```
+
+
### Diagrams
The
diff --git a/docs/source/dev/hwobj_signals.md b/docs/source/dev/hwobj_signals.md
new file mode 100644
index 0000000000..e5b1f977ec
--- /dev/null
+++ b/docs/source/dev/hwobj_signals.md
@@ -0,0 +1,415 @@
+# MXCuBECore HardwareObject signals
+
+MXCuBE relies heavily on signals being emitted and listened to by many elements.
+For example, a hardware object may listen to other lower level hardware objects
+in order to update values after some calculation.
+But it is also critical in the UI (both web and Qt),
+in both cases they expect periodic signal updates
+for displaying the most recent information to the user
+(e.g. motor positions, data collection state, etc.)
+
+
+## Implementation
+
+Depending on the installed modules,
+signals are emitted using [Louie](https://pypi.org/project/Louie/)
+or [PyDispatcher](https://pypi.org/project/PyDispatcher/).
+The former being based on the latter.
+The developer does not need to deal with the differences between those two modules
+as it is already being handled in the file `mxcubecore/dispatcher.py`.
+
+:::{note}
+Can we remove any of those dependencies?
+:::
+
+{attribution="[PyDispatcher](https://pypi.org/project/PyDispatcher/2.0.7/)"}
+> PyDispatcher provides the Python programmer with a
+ multiple-producer-multiple-consumer signal registration and routing infrastructure
+ for use in multiple contexts.
+
+When certain events or conditions occur within a hardware object,
+corresponding signals are emitted
+to inform connected components or modules about these changes.
+
+The {py:class}`mxcubecore.BaseHardwareObjects.HardwareObject` class
+serves as the base class for all hardware objects in MXCuBE.
+It includes methods for defining and emitting signals,
+allowing derived classes to customize signal emission
+based on their specific requirements.
+
+:::{note}
+Strictly speaking it is the `HardwareObject` or `HardwareObjectYaml` class
+(both inherit from `HardwareObjectMixin`).
+Once we unify the YAML and XML configuration,
+this distinction should hopefully disappear.
+:::
+
+
+### Emit
+
+Signals are typically emitted when the state of a hardware object changes,
+such as when it becomes ready for operation, encounters an error, or completes a task.
+Additionally, signals may be emitted
+to indicate changes in parameters or settings of the hardware,
+such as new setpoints, values, or configuration options.
+
+To emit a signal, derived classes can use the
+{py:meth}`mxcubecore.BaseHardwareObjects.HardwareObjectMixin.emit`
+method provided by the {py:class}`HardwareObject` class.
+This method takes the name of the signal as an argument
+and optionally includes additional data or parameters to pass along with the signal.
+This method calls the `dispatcher.send` method.
+
+From the {py:class}`mxcubecore.BaseHardwareObjects.HardwareObjectMixin` class
+(removing extra lines for brevity):
+
+```python
+ def emit(self, signal: Union[str, object, Any], *args) -> None:
+ signal = str(signal)
+
+ if len(args) == 1:
+ if isinstance(args[0], tuple):
+ args = args[0]
+ dispatcher.send(signal, self, *args)
+```
+
+So, in a custom hardware object, since it inherits from
+{py:class}`mxcubecore.BaseHardwareObjects.HardwareObject`,
+one only needs to call:
+
+```python
+self.emit('my_signal', new_value)
+```
+
+
+### Receive
+
+{py:class}`mxcubecore.BaseHardwareObjects.HardwareObjectMixin`
+implements the following `connect` method,
+built around the homonymous method of _PyDispatcher_.
+Making it more convenient to use.
+The functions provides syntactic sugar:
+instead of `self.connect(self, "signal", slot)`
+it is possible to do `self.connect("signal", slot)`.
+
+From the {py:meth}`HardwareObjectMixin.connect` method
+(removing extra lines for brevity):
+
+```python
+ def connect(
+ self,
+ sender: Union[str, object, Any],
+ signal: Union[str, Any],
+ slot: Optional[Callable] = None,
+ ) -> None:
+ """Connect a signal sent by self to a slot.
+
+ Args:
+ sender (Union[str, object, Any]): If a string, interprted as the signal.
+ signal (Union[str, Any]): In practice a string, or dispatcher.
+ Any if sender is a string interpreted as the slot.
+ slot (Optional[Callable], optional): In practice a functon or method.
+ Defaults to None.
+
+ Raises:
+ ValueError: If slot is None and "sender" parameter is not a string.
+ """
+
+ if slot is None:
+ if isinstance(sender, str):
+ slot = signal
+ signal = sender
+ sender = self
+ else:
+ raise ValueError("invalid slot (None)")
+
+ signal = str(signal)
+
+ dispatcher.connect(slot, signal, sender)
+
+ self.connect_dict[sender] = {"signal": signal, "slot": slot}
+
+ if hasattr(sender, "connect_notify"):
+ sender.connect_notify(signal)
+```
+
+And an example usage on a custom hardware object would be:
+
+```python
+self.connect(some_other_hwobj, "a_signal", callback_method)
+```
+
+This assumes that `some_other_hwobj`
+is linked in the custom hardware object initialization,
+and `callback_method` must exist,
+otherwise an exception will happen once the signal is received.
+
+If the sender hardware object has a method named `connect_notify`,
+it will be called on connect.
+Since this connect happens at application initialization,
+this typically triggers the emission of all signals during initialization,
+and thus all receivers start with the most recent value.
+
+
+## Basic example
+
+Given two hardware objects:
+
+```python
+from mxcubecore.BaseHardwareObjects import HardwareObject
+import gevent
+from gevent import monkey; monkey.patch_all(thread=False)
+import random
+import datetime
+
+class HO1(HardwareObject):
+ """
+
+ """
+
+ def __init__(self, name):
+ super().__init__(name)
+ self._value = 0.0
+ self.run = False
+
+ def get_value(self):
+ return self._value
+
+ def update_value(self):
+ while self.run:
+ _new_val = random.random()
+ self._value = _new_val
+ print(f'{datetime.datetime.now()} | valueChanged emitted, new value: {self._value}')
+ self.emit("valueChanged", self._value)
+ gevent.sleep(3)
+
+ def start(self):
+ self.run = True
+ gevent.spawn(self.update_value)
+
+ def stop(self):
+ self.run = False
+```
+
+and a data consumer:
+
+```python
+from mxcubecore.BaseHardwareObjects import HardwareObject
+import datetime
+
+class HO2(HardwareObject):
+ """
+
+ """
+
+
+ def __init__(self, name):
+ super().__init__(name)
+ self._value = 0.0
+ self.ho1 = None
+
+ def init(self):
+ self.ho1 = self.get_object_by_role("ho1")
+ self.connect(self.ho1, "valueChanged", self.callback)
+
+ def callback(self, *args):
+ print(f"{datetime.datetime.now()} | valueChanged callback, arguments: {args}")
+```
+
+One could run both:
+
+```none
+In [1]: from mxcubecore import HardwareRepository as hwr
+ ...: hwr_dir='mxcubecore/configuration/mockup/test/'
+ ...: hwr.init_hardware_repository(hwr_dir)
+ ...: hwrTest = hwr.get_hardware_repository()
+ ...: ho1 = hwrTest.get_hardware_object("/ho1")
+ ...: ho2 = hwrTest.get_hardware_object("/ho2")
+2024-03-18 12:20:18,434 |INFO | Hardware repository: ['/Users/mikegu/Documents/MXCUBE/mxcubecore_upstream/mxcubecore/configuration/mockup/test']
++======================================================================================+
+| role | Class | file | Time (ms)| Comment
++======================================================================================+
+| beamline | Beamline | beamline_config.yml | 9 | Start loading contents:
+| mock_procedure | None | procedure-mockup.yml | 0 | File not found
+| beamline | Beamline | beamline_config.yml | 9 | Done loading contents
++======================================================================================+
+
+In [2]: ho1.start()
+
+2024-03-18 12:21:15.401871 | valueChanged emitted, new value: 0.7041173058901172
+2024-03-18 12:21:15.402110 | valueChanged callback, arguments: (0.7041173058901172,)
+2024-03-18 12:21:18.407419 | valueChanged emitted, new value: 0.39293503718591827
+2024-03-18 12:21:18.407770 | valueChanged callback, arguments: (0.39293503718591827,)
+2024-03-18 12:21:21.411648 | valueChanged emitted, new value: 0.8190801968640632
+2024-03-18 12:21:21.411897 | valueChanged callback, arguments: (0.8190801968640632,)
+2024-03-18 12:21:24.417379 | valueChanged emitted, new value: 0.5170546126120815
+2024-03-18 12:21:24.418428 | valueChanged callback, arguments: (0.5170546126120815,)
+2024-03-18 12:21:27.420696 | valueChanged emitted, new value: 0.27400475091220955
+2024-03-18 12:21:27.421434 | valueChanged callback, arguments: (0.27400475091220955,)
+2024-03-18 12:21:30.426785 | valueChanged emitted, new value: 0.3473955083798488
+2024-03-18 12:21:30.427018 | valueChanged callback, arguments: (0.3473955083798488,)
+2024-03-18 12:21:33.427715 | valueChanged emitted, new value: 0.9503048610962694
+2024-03-18 12:21:33.427902 | valueChanged callback, arguments: (0.9503048610962694,)
+In [3]: ho1.stop()
+```
+
+As can be seen, the second hardware object receives and processes first one's signal.
+
+:::{note}
+At least one entry must appear in the beamline's YAML configuration file.
+In this case only the procedure mockup is present, all the other mockups are commented.
+That is why only a few items appear in the loading table.
+:::
+
+
+## General signals List
+
+The following tables list the generic signals
+as well as the signals from some of the most important hardware objects.
+
+:::{note}
+Additional signals could be emitted by other hardware objects.
+For example, `"energyScanFinished"` by the energy scan object, and similar.
+For the sake of keeping this document digestable not all the signals are listed.
+:::
+
+
+### State related
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| stateChanged | Notifies when the state has changed, new state value emitted | ('stateChanged', newState) | |
+| specificStateChanged | Notifies when a particular state has changed, new state value emitted | ('stateChanged', newState) | Defined in HardwareObjectMixin, only used in AbstractDetector |
+| deviceReady | Notifies that the device is now ready | 'deviceReady' | **Deprecated**|
+| deviceNotReady | Notifies that the device is now not ready | 'deviceNotReady' | **Deprecated**|
+| equipmentReady | Notifies that the device is now ready | 'equipmentReady' | **Deprecated**|
+| equipmentNotReady | Notifies that the device is now not ready | 'equipmentNotReady' | **Deprecated**|
+
+
+### Value related
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| valueChanged | Notifies when the value has changed | ('valueChanged', newValue) | |
+| update | Notifies when the value has changed | ('update', newValue) | **Deprecated**|
+| limitsChanged | Notifies when the limits have changed | ('limitsChanged', (low, high)) | |
+
+
+### Data collection related
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| energyScanStarted | | "energyScanStarted" | |
+| energyScanFinished | | "energyScanFinished", dict: energyScanParameters | |
+| collectReady | collect hwobj readiness | "collectReady", bool | |
+| collectOscillationStarted | | "collectOscillationStarted", (owner, sampleIid, sampleCode, sampleLocation, dataCollectParameters, oscId) | |
+| collectOscillationFinished | | "collectOscillationFinished", (owner, True, msgg, collectionId, oscId, dataCollectParameters) | |
+| collectOscillationFailed | | "collectOscillationFailed", (owner, False, msg, collectionId, osc_id)| |
+| collectEnded | | "collectEnded", (owner, bool, msg) | in AbstractMultiCollect|
+| progressInit | | "progressInit", ("Collection", 100, False)| |
+| progressStop | | "progressStop"| |
+| collectImageTaken | | ("collectImageTaken", frameNumber) | |
+| collectNumberOfFrames | | ("collectNumberOfFrames", nframes, exposure_time) |in AbstractMultiCollect |
+
+
+### Queue
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| child\_added | | "child\_added", (parent_node, child_node) | |
+| child\_removed | | "child\_removed", (parent, child)) | |
+| statusMessage | | "statusMessage", ("status", "message"", "message") | Unclear purpose, arbitrary usage of the messages |
+| queue\_execution\_finished | | "queue\_execution\_finished" | |
+| queue\_execution\_finished | | "queue\_execution\_finished" | |
+| queue\_entry\_execute\_started | | "queue\_entry\_execute\_started", entry | |
+| queue\_entry\_execute\_finished | | "queue\_entry\_execute\_finished", statusMessage | |
+| queue\_stopped | | "queue\_stopped" | |
+| queue\_paused | | "queue\_paused" | |
+| show\_workflow\_tab | | "show\_workflow\_tab" | |
+
+
+### Difractometer
+
+Useful data types:
+
+```python
+centring_status = {
+ "valid": bool,
+ "startTime": str, # "%Y-%m-%d %H:%M:%S"
+ "startTime": str, # "%Y-%m-%d %H:%M:%S"
+ "angleLimit": bool,
+ "motors": MotorsDict, # see below
+ "accepted": bool,
+}
+
+motor_positions = {
+ "phi": float,
+ "phiy": float,
+ "phiz": float,
+ "sampx": float,
+ "sampy": float,
+ "kappa": float,
+ "kappa_phi": float,
+ "phi": float,
+ "zoom": float?, # optional
+ "beam_x": float,
+ "beam_y": float
+}
+```
+
+
+#### `GenericDiffractometer.py`
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| minidiffTransferModeChanged | | "minidiffTransferModeChanged", mode | |
+| minidiffPhaseChanged | | "minidiffPhaseChanged", currentPhase | |
+| newAutomaticCentringPoint | | "newAutomaticCentringPoint", motorPositions | |
+| centringInvalid | | "centringInvalid"| |
+| centringStarted | | "centringStarted", (method, False) | |
+| centringMoving | | "centringMoving" | |
+| centringFailed | | "centringFailed", (method, dict:centring_status) | |
+| centringSuccessful | | "centringSuccessful", (method, dict:centring_status) | |
+| newAutomaticCentringPoint | | "newAutomaticCentringPoint", motorPos | |
+| centringAccepted | | "centringAccepted", (bool: accepted, dict:centring_status) | |
+| fsmConditionChanged | | "fsmConditionChanged", (message, bool) |Also emitted in collect so unclear purpose |
+| progressMessage | | "progressMessage", msg | |
+| diffractometerMoved | | "diffractometerMoved" | |
+| pixelsPerMmChanged | | "pixelsPerMmChanged", (pixels\_per\_mm\_x, pixels\_per\_mm\_y) | |
+| zoomMotorStateChanged | | "zoomMotorStateChanged", state | |
+| zoomMotorPredefinedPositionChanged | | "zoomMotorPredefinedPositionChanged", (position_name, offset) | |
+| minidiffStateChanged | | "minidiffStateChanged", state | |
+| minidiffReady | | "minidiffReady" | |
+| minidiffNotReady | | "minidiffNotReady" | |
+| minidiffSampleIsLoadedChanged | | "minidiffSampleIsLoadedChanged", sampleIsLoaded | |
+| minidiffHeadTypeChanged | | "minidiffHeadTypeChanged", headType | |
+| minidiffNotReady | | "minidiffNotReady" | |
+
+
+#### `Minidiff.py`
+
+| Signal | Description | Signature | Notes |
+| ---------------------- | ----------- | --------- | ------ |
+| diffractometerMoved | | "diffractometerMoved" | |
+| minidiffReady | | "minidiffReady" | |
+| minidiffNotReady | | "minidiffNotReady" | |
+| minidiffStateChanged | | "minidiffStateChanged", state | |
+| zoomMotorStateChanged | | "zoomMotorStateChanged", state | |
+| zoomMotorPredefinedPositionChanged | | "zoomMotorPredefinedPositionChanged", (position_name, offset) | |
+| phiMotorStateChanged | | "phiMotorStateChanged", state | |
+| phizMotorStateChanged | | "phizMotorStateChanged", state | |
+| phiyMotorStateChanged | | "phiyMotorStateChanged", state | |
+| sampxMotorStateChanged | | "sampxMotorStateChanged", state | |
+| sampyMotorStateChanged | | "sampyMotorStateChanged", state | |
+| centringInvalid | | "centringInvalid"| |
+| centringStarted | | "centringStarted", (method, False) | |
+| centringMoving | | "centringMoving" | |
+| centringFailed | | "centringFailed", (method, dict:centring_status) | |
+| centringSuccessful | | "centringSuccessful", (method, dict:centring_status) | |
+| newAutomaticCentringPoint | | "newAutomaticCentringPoint", motorPos | |
+| centringAccepted | | "centringAccepted", (bool: accepted, dict:centring_status) | |
+| centringSnapshots | | "centringSnapshots", boolean | |
+| progressMessage | | "progressMessage", msg | |
diff --git a/mxcubecore/HardwareObjects/abstract/AbstractActuator.py b/mxcubecore/HardwareObjects/abstract/AbstractActuator.py
index e85b22ec51..a20c538399 100644
--- a/mxcubecore/HardwareObjects/abstract/AbstractActuator.py
+++ b/mxcubecore/HardwareObjects/abstract/AbstractActuator.py
@@ -18,12 +18,9 @@
# You should have received a copy of the GNU General Lesser Public License
# along with MXCuBE. If not, see .
-"""Abstract Actuator class.
-Defines the set/update value, get/set/update limits and validate_value
-methods and the get_value and _set_value abstract methods.
-Initialises the actuator_name, username, read_only and default_value properties.
-Emits signals valueChanged and limitsChanged.
-"""
+"""Abstract Actuator."""
+
+from __future__ import annotations
import abc
import math
@@ -38,13 +35,43 @@
class AbstractActuator(HardwareObject):
- """Abstract actuator"""
+ """Abstract actuator defines methods common to all moving devices.
+
+ The ``_set_value`` method is the only abtract method that needs to be overloaded
+ in each implementation.
+
+ Attributes:
+ _nominal_value (float | None):
+ Current actuator value.
+ default_value (float | None):
+ Value specified by XML property, otherwise ``None``.
+ _nominal_limits (tuple[float | None, float | None]):
+ Values specified by XML property, otherwise ``None``.
+ actuator_name (str | None):
+ Actuator name specified by XML property, otherwise ``None``.
+ read_only (bool):
+ Read-only flag specified by XML property, otherwise ``False``.
+ username (str):
+
+ Emits:
+ valueChanged (tuple[int]):
+ Tuple whose first and only item is the new value.
+ Emitted during initialization of the hardware object
+ and when setting a new value.
+ limitsChanged (tuple[tuple[int, int]]):
+ Tuple whose first and only item is a two-item tuple of the new limits
+ (low limit first and high limit second).
+ Emitted by ``update_limits`` if limit values are changed.
+ stateChanged (tuple):
+ Tuple whose first and only item is the new state.
+ Emitted by ``force_emit_signals``
+ """
__metaclass__ = abc.ABCMeta
unit = None
- def __init__(self, name):
+ def __init__(self, name: str):
super().__init__(name)
self._nominal_value = None
self._nominal_limits = (None, None)
@@ -55,9 +82,7 @@ def __init__(self, name):
self._lock = RLock()
def init(self):
- """Initialise actuator_name, username, read_only and default_value
- properties.
- """
+ """Init properties: actuator_name, username, read_only and default_value."""
self.actuator_name = self.get_property("actuator_name")
self.read_only = self.get_property("read_only") or False
self.default_value = self.get_property("default_value")
@@ -74,24 +99,28 @@ def init(self):
@abc.abstractmethod
def get_value(self):
"""Read the actuator position.
+
Returns:
- value: Actuator position.
+ Actuator position.
"""
return None
def get_limits(self):
"""Return actuator low and high limits.
+
Returns:
- (tuple): two elements (low limit, high limit) tuple.
+ (tuple): Two-item tuple (low limit, high limit).
"""
return self._nominal_limits
- def set_limits(self, limits):
- """Set actuator low and high limits. Emits signal limitsChanged.
+ def set_limits(self, limits: tuple) -> None:
+ """Set actuator low and high limits and emit signal ``limitsChanged``.
+
Args:
- limits (tuple): two elements (low limit, high limit) tuple.
+ limits (tuple): Two-item tuple (low limit, high limit).
+
Raises:
- ValueError: Attempt to set limits for read-only Actuator.
+ ValueError: Attempt to set limits for read-only actuator.
"""
if self.read_only:
raise ValueError("Attempt to set limits for read-only Actuator")
@@ -99,12 +128,14 @@ def set_limits(self, limits):
self._nominal_limits = limits
self.emit("limitsChanged", (self._nominal_limits,))
- def validate_value(self, value):
+ def validate_value(self, value) -> bool:
"""Check if the value is within limits.
+
Args:
- value(numerical): value
+ value(numerical): Value.
+
Returns:
- (bool): True if within the limits
+ ``True`` if within the limits, ``False`` otherwise.
"""
if value is None:
return True
@@ -117,21 +148,24 @@ def validate_value(self, value):
@abc.abstractmethod
def _set_value(self, value):
"""Implementation of specific set actuator logic.
+
Args:
- value: target value
+ value: Target value.
"""
- def set_value(self, value, timeout=0):
+ def set_value(self, value, timeout: float = 0) -> None:
"""Set actuator to value.
+
+ If ``timeout == 0``: return at once and do not wait (default).
+ If ``timeout is None``: wait forever.
+
Args:
value: target value
- timeout (float): optional - timeout [s],
- If timeout == 0: return at once and do not wait
- (default);
- if timeout is None: wait forever.
+ timeout (float): Optional timeout in seconds. Default is ``0``: do not wait.
+
Raises:
ValueError: Invalid value or attemp to set read only actuator.
- RuntimeError: Timeout waiting for status ready # From wait_ready
+ RuntimeError: Timeout waiting for status ready (from ``wait_ready``):
"""
with self._lock:
@@ -146,10 +180,11 @@ def set_value(self, value, timeout=0):
else:
raise ValueError(f"Invalid value {value}")
- def update_value(self, value=None):
- """Check if the value has changed. Emits signal valueChanged.
+ def update_value(self, value=None) -> None:
+ """Check if the value has changed and emit signal ``valueChanged``.
+
Args:
- value: value
+ value: Value.
"""
if value is None:
value = self.get_value()
@@ -158,10 +193,11 @@ def update_value(self, value=None):
self._nominal_value = value
self.emit("valueChanged", (value,))
- def update_limits(self, limits=None):
- """Check if the limits have changed. Emits signal limitsChanged.
+ def update_limits(self, limits=None) -> None:
+ """Check if the limits have changed and emit signal ``limitsChanged``.
+
Args:
- limits (tuple): two elements tuple (low limit, high limit).
+ limits (tuple): Two-item tuple (low limit, high limit).
"""
if not limits:
limits = self.get_limits()
@@ -172,17 +208,17 @@ def update_limits(self, limits=None):
self._nominal_limits = limits
self.emit("limitsChanged", (limits,))
- def re_emit_values(self):
- """Update values for all internal attributes"""
+ def re_emit_values(self) -> None:
+ """Update values for all internal attributes."""
self.update_value(self.get_value())
self.update_limits(self.get_limits())
super(AbstractActuator, self).re_emit_values()
- def force_emit_signals(self):
- """Forces to emit all signals.
+ def force_emit_signals(self) -> None:
+ """Force emission of all signals.
- Method is called from gui
- Do not call it within HWR
+ Method is called from GUI.
+ Do not call it from within a hardware object.
"""
self.emit("valueChanged", (self.get_value(),))
self.emit("limitsChanged", (self.get_limits(),))
diff --git a/mxcubecore/HardwareObjects/abstract/AbstractAperture.py b/mxcubecore/HardwareObjects/abstract/AbstractAperture.py
index 5193ecc4a6..cec9751ef4 100644
--- a/mxcubecore/HardwareObjects/abstract/AbstractAperture.py
+++ b/mxcubecore/HardwareObjects/abstract/AbstractAperture.py
@@ -18,6 +18,10 @@
# along with MXCuBE. If not, see .
+"""Abstract hardware object for the aperture."""
+
+from __future__ import annotations
+
import logging
from warnings import warn
@@ -28,7 +32,16 @@
class AbstractAperture(HardwareObject):
- def __init__(self, name):
+ """Abstract hardware object for the aperture.
+
+ Emits:
+ diameterIndexChanged (int, float):
+ Two-item tuple: current index and diameter in millimeters.
+ valueChanged (str):
+ Current position name.
+ """
+
+ def __init__(self, name: str) -> None:
warn(
"AbstractAperture is deprecated. Use AbstractNState instead",
DeprecationWarning,
@@ -51,36 +64,35 @@ def init(self):
except Exception:
logging.getLogger("HWR").error("Aperture: no position list defined")
- def get_diameter_size_list(self):
- """
+ def get_diameter_size_list(self) -> list[float]:
+ """Get list of diameter sizes.
+
Returns:
- list: list of diameter sizes in microns
+ List of diameter sizes in microns.
"""
return self._diameter_size_list
- def get_position_list(self):
- """
+ def get_position_list(self) -> list[str]:
+ """Get list of positions.
+
Returns:
- list: list of position names as str
+ Position names as a list of strings.
"""
return self._position_list
- def get_diameter_index(self):
- """
+ def get_diameter_index(self) -> int:
+ """Get current diameter index.
+
Returns:
- int: current diameter index
+ Current diameter index.
"""
return self._current_diameter_index
- def set_diameter_index(self, diameter_index):
- """
- Sets active diameter index
+ def set_diameter_index(self, diameter_index: int) -> None:
+ """Set active diameter index.
Args:
- diameter_index (int): selected diameter index
-
- Emits:
- diameterIndexChanged (int, float): current index, diameter in mm
+ diameter_index: Selected diameter index.
"""
if diameter_index < len(self._diameter_size_list):
self._current_diameter_index = diameter_index
@@ -94,17 +106,19 @@ def set_diameter_index(self, diameter_index):
"Aperture: Diameter index %d is not valid" % diameter_index
)
- def get_diameter_size(self):
- """
+ def get_diameter_size(self) -> float:
+ """Get diameter size.
+
Returns:
- float: current diameter size in mm
+ Current diameter size in millimeters.
"""
return self._diameter_size_list[self._current_diameter_index]
- def set_diameter_size(self, diameter_size):
- """
+ def set_diameter_size(self, diameter_size: int) -> None:
+ """Set diameter size.
+
Args:
- diameter_size (int): selected diameter index
+ diameter_size: selected diameter index
"""
if diameter_size in self._diameter_size_list:
self.set_diameter_index(self._diameter_size_list.index(diameter_size))
@@ -113,26 +127,26 @@ def set_diameter_size(self, diameter_size):
"Aperture: Selected diameter is not in the diameter list"
)
- def get_position_name(self):
- """
+ def get_position_name(self) -> str:
+ """Get current position name.
+
Returns:
- str: current position as str
+ Current position name.
"""
return self._current_position_name
- def set_position(self, position_index):
+ def set_position(self, position_index: int) -> None:
warn(
"set_position is deprecated. Use set_position_index(position_index) instead",
DeprecationWarning,
)
self.set_position_index(position_index)
- def set_position_name(self, position_name):
- """
- Sets aperture position based on a position name
+ def set_position_name(self, position_name: str) -> None:
+ """Set aperture position based on a position name.
Args:
- position_name (str): selected position
+ position_name: Selected position name.
"""
if position_name in self._position_list:
self._current_position_name = position_name
@@ -142,12 +156,11 @@ def set_position_name(self, position_name):
"Aperture: Position %s is not in the position list" % position_name
)
- def set_position_index(self, position_index):
- """
- Sets aperture position based on a position index
+ def set_position_index(self, position_index: int) -> None:
+ """Set aperture position based on a position index.
Args:
- position_index (int): selected position index
+ position_index: Selected position index.
"""
if position_index < len(self._position_list):
self._current_position_name = self._position_list[position_index]
@@ -158,28 +171,19 @@ def set_position_index(self, position_index):
)
def set_in(self):
- """
- Sets aperture in the beam
- """
- pass
+ """Set aperture in the beam."""
def set_out(self):
- """
- Removes aperture from the beam
- """
- pass
+ """Remove aperture from the beam."""
- def is_out(self):
+ def is_out(self) -> bool:
"""
Returns:
- bool: True if aperture is in the beam, otherwise returns false
+ ``True`` if aperture is in the beam, otherwise returns ``False``.
"""
- pass
- def force_emit_signals(self):
- """
- Reemits all signals
- """
+ def force_emit_signals(self) -> None:
+ """Reemit all signals."""
self.emit("valueChanged", self._current_position_name)
self.emit(
"diameterIndexChanged",
diff --git a/mxcubecore/HardwareObjects/abstract/AbstractBeam.py b/mxcubecore/HardwareObjects/abstract/AbstractBeam.py
index 5d7c0ed2c2..6f76370290 100644
--- a/mxcubecore/HardwareObjects/abstract/AbstractBeam.py
+++ b/mxcubecore/HardwareObjects/abstract/AbstractBeam.py
@@ -18,13 +18,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with MXCuBE. If not, see .
-"""
-AbstractBeam class - methods to define the size and shape of the beam.
+"""Abstract beam hardware object."""
-emits:
-- beamSizeChanged (self._beam_width, self._beam_height)
-- beamInfoChanged (self._beam_info_dict.copy())
-"""
+from __future__ import annotations
__copyright__ = """ Copyright © by MXCuBE Collaboration """
__license__ = "LGPLv3+"
@@ -51,11 +47,33 @@ class BeamShape(Enum):
class AbstractBeam(HardwareObject):
- """AbstractBeam class"""
+ """Abstract beam hardware object.
+
+ Has methods to define the size and shape of the beam.
+
+ Emits:
+ beamSizeChanged(tuple[float, float]):
+ Two-item tuple of beam width and beam height in micrometers
+ emitted when the beam size has changed.
+ beamInfoChanged(dict):
+ Dictionary containing beam info emitted when the beam info has changed.
+
+ Attributes:
+ _aperture: reference to the aperture hardware object
+ _slits: reference to the slits hardware object
+ _definer: reference to the slits hardware object
+ _beam_size_dict (dict): dictionary containing min max of aperure, slits and definer
+ _beam_width (float): beam size in horizontal direction
+ _beam_height (float): beam size in vertical direction
+ _beam_shape (str): beam shape (rectangular, ellipse, unknown)
+ _beam_divergence (tuple): beam divergence in horizontal and vertical directions
+ _beam_position_on_screen (tuple): beam position in pixel units
+ _beam_info_dict (dict): dictionary containing size_x, size_y, shape, label
+ """
__metaclass__ = abc.ABCMeta
- def __init__(self, name):
+ def __init__(self, name) -> None:
super().__init__(name)
self._aperture = None
@@ -82,10 +100,8 @@ def __init__(self, name):
"label": self._beam_label,
}
- def init(self):
- """
- Initialise default values and objects
- """
+ def init(self) -> None:
+ """Initialise default values and objects."""
super().init()
_divergence_vertical = self.get_property("beam_divergence_vertical")
_divergence_horizontal = self.get_property("beam_divergence_horizontal")
@@ -95,88 +111,86 @@ def init(self):
@property
def aperture(self):
- """
- Returns aperture hwobj
- """
+ """Aperture hardware object."""
return self._aperture
@property
def slits(self):
- """
- Returns slits hwobj
- """
+ """Slits hardware object."""
return self._slits
@property
def definer(self):
- """
- Beam definer device, equipment like focusing optics, CRLs, and etc.
- """
+ """Beam definer device, equipment like focusing optics, CRLs, and etc."""
return self._definer
- def get_beam_divergence(self):
+ def get_beam_divergence(self) -> tuple:
"""Get the beam divergence.
+
Returns:
- (tuple): Beam divergence (horizontal, vertical) [μm]
+ Beam divergence (horizontal, vertical) in micrometers.
"""
return self._beam_divergence
- def get_available_size(self):
+ def get_available_size(self) -> dict:
"""Get the available beam definers configuration.
+
Returns:
- (dict): Dictionary {"type": (list), "values": (list)}, where
- "type": the definer type ("aperture", "slits","definer")
- "values": List of available beam size difinitions,
- according to the "type".
- Raises:
- NotImplementedError
+ Dictionary ``{"type": (list), "values": (list)}`` where
+ ``type`` is the definer type ``("aperture", "slits","definer")`` and
+ ``values`` is a list of available beam size definitions
+ according to ``type``.
"""
raise NotImplementedError
- def get_defined_beam_size(self):
+ def get_defined_beam_size(self) -> dict:
"""Get the predefined beam labels and size.
+
Returns:
- (dict): Dictionary wiith list of avaiable beam size labels
- and the corresponding size (width,height) tuples.
- {"label": [str, str, ...], "size": [(w,h), (w,h), ...]}
- Raises:
- NotImplementedError
+ Dictionary with list of available beam size labels
+ and the corresponding size (width,height) tuples.
+ ``{"label": [str, str, ...], "size": [(w,h), (w,h), ...]}``.
"""
raise NotImplementedError
- def get_beam_shape(self):
- """
+ def get_beam_shape(self) -> BeamShape:
+ """Get beam shape.
+
Returns:
- beam_shape: Enum BeamShape
+ Beam shape.
"""
self.evaluate_beam_info()
return self._beam_shape
- def get_beam_size(self):
- """
+ def get_beam_size(self) -> tuple[float, float]:
+ """Get beam size.
+
Returns:
- (tuple): two floats
+ Two-item tuple: width and height.
"""
self.evaluate_beam_info()
return self._beam_width, self._beam_height
- def set_value(self, size=None):
+ def set_value(self, size: list[float] | str | None = None) -> None:
"""Set the beam size.
Args:
- size (list): Width, heigth [um] or
- (str): Aperture or definer name.
- Raises:
- NotImplementedError
+ size: List of width and heigth in micrometers or
+ aperture or definer name as string.
"""
raise NotImplementedError
- def set_beam_size_shape(self, beam_width, beam_height, beam_shape):
- """
- Sets beam size and shape
+ def set_beam_size_shape(
+ self,
+ beam_width: float,
+ beam_height: float,
+ beam_shape: BeamShape,
+ ) -> None:
+ """Set beam size and shape.
+
Args:
- beam_width (float): requested beam width in microns
- beam_height (float): requested beam height in microns
- beam_shape (BeamShape enum): requested beam shape
+ beam_width: Requested beam width in microns.
+ beam_height: Requested beam height in microns.
+ beam_shape: Requested beam shape.
"""
warn(
"set_beam_size_shape is deprecated. Use set_value() instead",
@@ -189,36 +203,39 @@ def set_beam_size_shape(self, beam_width, beam_height, beam_shape):
elif beam_shape == BeamShape.ELLIPTICAL:
self._aperture.set_diameter_size(beam_width)
- def get_beam_position_on_screen(self):
- """Get the beam position
+ def get_beam_position_on_screen(self) -> tuple:
+ """Get the beam position.
+
Returns:
- (tuple): Position (x, y) [pixel]
+ X and Y coordinates of the beam position in pixels.
"""
# (TODO) move this method to AbstractSampleView
return self._beam_position_on_screen
- def set_beam_position_on_screen(self, beam_x_y):
- """Set the beam position
- Returns:
- beam_x_y (tuple): Position (x, y) [pixel]
+ def set_beam_position_on_screen(self, beam_x_y: tuple) -> None:
+ """Set the beam position.
+
+ Args:
+ beam_x_y: X and Y coordinates of the beam position in pixels.
"""
raise NotImplementedError
- def get_beam_info_dict(self):
- """
+ def get_beam_info_dict(self) -> dict:
+ """Get beam info dictionary.
+
Returns:
- (dict): copy of beam_info_dict
+ Copy of beam info dictionary.
"""
return self._beam_info_dict.copy()
- def evaluate_beam_info(self):
- """
- Method called if aperture, slits or focusing has been changed.
+ def evaluate_beam_info(self) -> dict:
+ """Method called if aperture, slits or focusing has been changed.
+
Evaluates which of the beam size defining devices determins the size.
+
Returns:
- (dict): Beam info dictionary (dict), type of the definer (str).
- {size_x: float, size_y: float,
- shape: BeamShape enum, label: str},
+ Beam info dictionary ``dict``, type of the definer ``str``.
+ ``{size_x: float, size_y: float, shape: BeamShape enum, label: str}``.
"""
_shape = BeamShape.UNKNOWN
_size = min(self._beam_size_dict.values())
@@ -248,9 +265,7 @@ def evaluate_beam_info(self):
return self._beam_info_dict
def re_emit_values(self):
- """
- Reemits beamSizeChanged and beamInfoChanged signals
- """
+ """Reemit ``beamSizeChanged`` and ``beamInfoChanged`` signals."""
HardwareObject.re_emit_values(self)
if self._beam_width != 9999 and self._beam_height != 9999:
self.emit("beamSizeChanged", (self._beam_width, self._beam_height))
diff --git a/mxcubecore/HardwareObjects/abstract/AbstractCharacterisation.py b/mxcubecore/HardwareObjects/abstract/AbstractCharacterisation.py
index 08f31d4168..4aa0883643 100644
--- a/mxcubecore/HardwareObjects/abstract/AbstractCharacterisation.py
+++ b/mxcubecore/HardwareObjects/abstract/AbstractCharacterisation.py
@@ -18,12 +18,7 @@
# You should have received a copy of the GNU General Lesser Public License
# along with MXCuBE. If not, see .
-"""Abstract Characterisation class.
-Defines:
-characterise, get_html_report methods, input_from_params, dc_from_output and
-get_default_characterisation_parameters abstract methods;
-prepare_input and is_running methods.
-"""
+"""Abstract hardware object class for characterisation."""
import abc
@@ -33,19 +28,21 @@
class AbstractCharacterisation(HardwareObject):
- """Abstract Characterisation Class"""
+ """Abstract hardware object class for characterisation."""
__metaclass__ = abc.ABCMeta
- def __init__(self, name):
+ def __init__(self, name) -> None:
super().__init__(name)
self.processing_done_event = gevent.event.Event()
@abc.abstractmethod
def characterise(self, _input):
"""Start the characterosation.
+
Args:
- input (object) Characterisation input object.
+ input (object): Characterisation input object.
+
Returns:
(str) The Characterisation result.
"""
@@ -54,8 +51,10 @@ def characterise(self, _input):
def get_html_report(self, output):
"""Get the path to the characterisation html report, generated by the
characterisation softare.
+
Args:
output (object): Characterisation output object
+
Returns:
(str) The path to the html result report.
"""
@@ -64,77 +63,81 @@ def get_html_report(self, output):
def input_from_params(self, ref_parameters, char_params, path_str):
"""
Args:
- ref_parameters: (A named tuple or object with following fields):
- 'id',
- 'prefix',
- 'run_number',
- 'template',
- 'first_image',
- 'num_images',
- 'osc_start',
- 'osc_range',
- 'overlap',
- 'exp_time',
- 'num_passes',
- 'comments',
- 'path',
- 'centred_positions',
- 'energy',
- 'resolution',
- 'transmission',
- 'shutterless',
- 'inverse_beam',
- 'screening_id'
-
- Args:
- char_params: (A named tuple or object with following fields):
- # Optimisation parameters
- 'aimed_resolution'
- 'aimed_multiplicity'
- 'aimed_i_sigma'
- 'aimed_completness'
- 'strategy_complexity'
- 'induce_burn'
- 'use_permitted_rotation'
- 'permitted_phi_start'
- 'permitted_phi_end'
-
- # Crystal
- 'max_crystal_vdim'
- 'min_crystal_vdim'
- 'max_crystal_vphi'
- 'min_crystal_vphi'
- 'space_group'
-
- # Characterisation type
- 'use_min_dose'
- 'use_min_time'
- 'min_dose'
- 'min_time'
- 'account_rad_damage'
- 'not_use_low_res'
- 'auto_res'
- 'opt_sad'
- 'sad_res'
- 'determine_rad_params'
-
- # Radiation damage model
- 'rad_suscept'
- 'beta'
- 'sigma'
-
- Args:
- path_str (Template string representing path to each image)
+ ref_parameters:
+ A named tuple or object with following fields:
+
+ 'id',
+ 'prefix',
+ 'run_number',
+ 'template',
+ 'first_image',
+ 'num_images',
+ 'osc_start',
+ 'osc_range',
+ 'overlap',
+ 'exp_time',
+ 'num_passes',
+ 'comments',
+ 'path',
+ 'centred_positions',
+ 'energy',
+ 'resolution',
+ 'transmission',
+ 'shutterless',
+ 'inverse_beam',
+ 'screening_id'
+
+ char_params:
+ A named tuple or object with following fields:
+
+ # Optimisation parameters
+ 'aimed_resolution'
+ 'aimed_multiplicity'
+ 'aimed_i_sigma'
+ 'aimed_completness'
+ 'strategy_complexity'
+ 'induce_burn'
+ 'use_permitted_rotation'
+ 'permitted_phi_start'
+ 'permitted_phi_end'
+
+ # Crystal
+ 'max_crystal_vdim'
+ 'min_crystal_vdim'
+ 'max_crystal_vphi'
+ 'min_crystal_vphi'
+ 'space_group'
+
+ # Characterisation type
+ 'use_min_dose'
+ 'use_min_time'
+ 'min_dose'
+ 'min_time'
+ 'account_rad_damage'
+ 'not_use_low_res'
+ 'auto_res'
+ 'opt_sad'
+ 'sad_res'
+ 'determine_rad_params'
+
+ # Radiation damage model
+ 'rad_suscept'
+ 'beta'
+ 'sigma'
+
+ path_str: Template string representing path to each image.
Returns:
- Input for characterisation software
+ Input for characterisation software.
"""
@abc.abstractmethod
def dc_from_output(self, output):
"""Create a data collection from characterisation result
+
Args:
- output (object) Characterisation result object
+ output (object): Characterisation result object
+
Returns:
(queue_model_objects.DataCollection)
"""
@@ -144,22 +147,25 @@ def get_default_characterisation_parameters(self, default_input_file):
"""
Args:
default_input_file (str): Path to file containing default input.
+
Returns:
(queue_model_objects.CharacterisationsParameters): object with
- default parameters.
+ default parameters.
"""
def prepare_input(self, _input):
- """Called by characterise before characterisation starts, can be used
- to manipulate edna_input object before characterisation starts.
+ """Called by characterise before characterisation starts.
+
+ Can be used to manipulate edna_input object before characterisation starts.
Example: to set a site specific output directory
+
Args:
- _input (object) Characterisation input object
+ _input (object): Characterisation input object.
"""
- def is_running(self):
+ def is_running(self) -> bool:
"""
Returns:
- (bool) True if process is running otherwise False
+ ``True`` if process is running otherwise ``False``.
"""
return not self.processing_done_event.is_set()
diff --git a/mxcubecore/HardwareObjects/abstract/AbstractCollect.py b/mxcubecore/HardwareObjects/abstract/AbstractCollect.py
index cc92eae71e..442aefd82d 100644
--- a/mxcubecore/HardwareObjects/abstract/AbstractCollect.py
+++ b/mxcubecore/HardwareObjects/abstract/AbstractCollect.py
@@ -18,10 +18,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with MXCuBE. If not, see .
-"""
-AbstractMulticollect
-Defines a sequence how data collection is executed.
-"""
+"""Abstract hardware object for data collection."""
import abc
import collections
@@ -66,9 +63,23 @@
class AbstractCollect(HardwareObject, object):
+ """Abstract hardware object for data collection.
+
+ Define a sequence in which data collection is executed.
+
+ Emits:
+ collectReady (bool):
+ collectOscillationStarted (dict):
+ progressInit (tuple[str, int, bool]):
+ For example: ``("Collection", progress, bool)``.
+ collectOscillationFailed (dict):
+ collectOscillationFinished (dict):
+ progressStop:
+ """
+
__metaclass__ = abc.ABCMeta
- def __init__(self, name):
+ def __init__(self, name) -> None:
HardwareObject.__init__(self, name)
self.bl_config = BeamlineConfig(*[None] * 18)
@@ -122,17 +133,23 @@ def init(self):
input_files_server=self.get_property("input_files_server"),
)
- def set_beamline_configuration(self, **configuration_parameters):
+ def set_beamline_configuration(self, **configuration_parameters) -> None:
"""Sets beamline configuration
- :param configuration_parameters: dict with config param.
- :type configuration_parameters: dict
+ Args:
+ configuration_parameters (dict): Configuration parameters.
"""
self.bl_config = BeamlineConfig(**configuration_parameters)
def collect(self, owner, dc_parameters_list):
- """
- Main collect method.
+ """Main collection method.
+
+ It spawns "do_collect" as well as ready event handling.
+
+ Args:
+ owner (str): Owner instance.
+ dc_parameters_list (list[dict]):
+ List of dictionary containing all collection parameters.
"""
self.ready_event.clear()
self.current_dc_parameters = dc_parameters_list[0]
@@ -142,8 +159,10 @@ def collect(self, owner, dc_parameters_list):
return self.data_collect_task
def do_collect(self, owner):
- """
- Actual collect sequence
+ """Actually collect the sequence.
+
+ Args:
+ owner (str): owner instance
"""
log = logging.getLogger("user_level_log")
log.info("Collection: Preparing to collect")
@@ -198,9 +217,9 @@ def do_collect(self, owner):
HWR.beamline.diffractometer.get_positions()
)
for motor in self.current_dc_parameters["motors"].keys():
- self.current_dc_parameters["motors"][
- motor
- ] = current_diffractometer_position.get(motor)
+ self.current_dc_parameters["motors"][motor] = (
+ current_diffractometer_position.get(motor)
+ )
# ----------------------------------------------------------------
# Move to the centered position and take crystal snapshots
@@ -224,9 +243,9 @@ def do_collect(self, owner):
energy = self.current_dc_parameters.get("energy")
detector_distance = self.current_dc_parameters.get("detector_distance")
try:
- resolution = self.current_dc_parameters.get("resolution").get("upper")
+ resolution = self.current_dc_parameters.get("resolution").get("upper")
except AttributeError:
- resolution = None
+ resolution = None
if wavelength:
# Wavelength (not having a default) overrides energy
@@ -250,12 +269,14 @@ def do_collect(self, owner):
detector_distance, wavelength
)
log.info(
- "Collection: Setting detector distance to %.2f", detector_distance,
+ "Collection: Setting detector distance to %.2f",
+ detector_distance,
)
self.set_resolution(resolution)
elif resolution:
log.info(
- "Collection: Setting resolution to %.2f", resolution,
+ "Collection: Setting resolution to %.2f",
+ resolution,
)
self.set_resolution(resolution)
@@ -372,69 +393,54 @@ def store_image_in_lims_by_frame_num(self, frame_number):
pass
def stop_collect(self):
- """
- Stops data collection
- """
+ """Stop data collection."""
if self.data_collect_task is not None:
self.data_collect_task.kill(block=False)
def open_detector_cover(self):
- """
- Descript. :
- """
- pass
+ """Open detector cover."""
def open_safety_shutter(self):
- """
- Descript. :
- """
- pass
+ """Open safety shutter."""
def open_fast_shutter(self):
- """
- Descript. :
- """
- pass
+ """Open fast shutter."""
def close_fast_shutter(self):
- """
- Descript. :
- """
- pass
+ """Close fast shutter."""
def close_safety_shutter(self):
- """
- Descript. :
- """
- pass
+ """Close safety shutter."""
def close_detector_cover(self):
- """
- Descript. :
- """
- pass
+ """Close detector cover."""
- def set_transmission(self, value):
- """
- Descript. :
- """
- pass
+ def set_transmission(self, value: float):
+ """Set transmission.
- def set_wavelength(self, value):
- """
- Descript. :
+ Args:
+ value: Transmission value to set.
"""
- pass
- def set_energy(self, value):
- """
- Descript. :
+ def set_wavelength(self, value: float):
+ """Set wavelength.
+
+ Args:
+ value: Wavelength value to set.
"""
- pass
- def set_resolution(self, value):
+ def set_energy(self, value: float):
+ """Set energy.
+
+ Args:
+ value: Energy value to set.
"""
- Descript. :
+
+ def set_resolution(self, value: float):
+ """Set resolution.
+
+ Args:
+ value: Resolution value to set.
"""
HWR.beamline.energy.wait_ready()
HWR.beamline.resolution.set_value(value)
@@ -442,79 +448,99 @@ def set_resolution(self, value):
def get_total_absorbed_dose(self):
return
- def get_wavelength(self):
- """
- Descript. :
+ def get_wavelength(self) -> float:
+ """Get current wavelength.
+
+ Returns:
+ Current beamline wavelength [Ä].
"""
if HWR.beamline.energy is not None:
return HWR.beamline.energy.get_wavelength()
- def get_detector_distance(self):
- """
- Descript. :
+ def get_detector_distance(self) -> float:
+ """Get current detector distance.
+
+ Returns:
+ Current detector distance position [mm].
"""
if HWR.beamline.detector is not None:
return HWR.beamline.detector.distance.get_value()
- def get_resolution(self):
- """
- Descript. :
+ def get_resolution(self) -> float:
+ """Get current resolution.
+
+ Returns:
+ Current resolution [Å].
"""
if HWR.beamline.resolution is not None:
return HWR.beamline.resolution.get_value()
- def get_transmission(self):
- """
- Descript. :
+ def get_transmission(self) -> float:
+ """Get current beamline transmission.
+
+ Returns:
+ Current transmission.
"""
if HWR.beamline.transmission is not None:
return HWR.beamline.transmission.get_value()
- def get_beam_size(self):
- """
- Descript. :
+ def get_beam_size(self) -> tuple:
+ """Get beam size.
+
+ Returns:
+ Beam width, and beam height.
"""
if HWR.beamline.beam is not None:
return HWR.beamline.beam.get_beam_size()
else:
return None, None
- def get_slit_gaps(self):
- """
- Descript. :
+ def get_slit_gaps(self) -> tuple:
+ """Get slit gap distance.
+
+ Returns:
+ Horizontal gap, and vertical gap.
"""
if HWR.beamline.beam is not None:
return HWR.beamline.beam.get_slits_gap()
return None, None
- def get_undulators_gaps(self):
- """
- Descript. :
+ def get_undulators_gaps(self) -> dict:
+ """Get undulator gaps.
+
+ Returns:
+ Undulator gaps.
"""
return {}
# return HWR.beamline.energy.get_undulator_gaps()
- def get_machine_current(self):
- """
- Descript. :
+ def get_machine_current(self) -> float:
+ """Get machine current.
+
+ Returns:
+ Machine current [mA].
"""
if HWR.beamline.machine_info:
return HWR.beamline.machine_info.get_current()
else:
return 0
- def get_machine_message(self):
- """
- Descript. :
+ def get_machine_message(self) -> str:
+ """Get machine message.
+
+ Returns:
+ Current machine message if any.
"""
if HWR.beamline.machine_info:
return HWR.beamline.machine_info.get_message()
else:
return ""
- def get_machine_fill_mode(self):
- """
- Descript. :
+ def get_machine_fill_mode(self) -> str:
+ """Get machine fill mode.
+
+ Returns:
+ Machine filling mode.
"""
if HWR.beamline.machine_info:
fill_mode = str(HWR.beamline.machine_info.get_message())
@@ -522,22 +548,26 @@ def get_machine_fill_mode(self):
else:
return ""
- def get_measured_intensity(self):
- """
- Descript. :
+ def get_measured_intensity(self) -> float:
+ """Get beam intensity.
+
+ Returns:
+ Beam intensity.
"""
return
- def get_cryo_temperature(self):
- """
- Descript. :
+ def get_cryo_temperature(self) -> float:
+ """Get cryogenic temperature.
+
+ Returns:
+ Current cryo temperature [K].
"""
return
def create_file_directories(self):
- """
- Method create directories for raw files and processing files.
- Directorie names for xds, mosflm and hkl are created
+ """Method to create directories for raw and processing files.
+
+ Directory names for xds, mosflm and hkl are created.
"""
self.create_directories(
self.current_dc_parameters["fileinfo"]["directory"],
@@ -549,8 +579,10 @@ def create_file_directories(self):
self.current_dc_parameters["xds_dir"] = xds_directory
def create_directories(self, *args):
- """
- Descript. :
+ """Creates folders on disk.
+
+ Args:
+ args (list(str)): List of directories to create.
"""
for directory in args:
try:
@@ -559,25 +591,27 @@ def create_directories(self, *args):
if e.errno != errno.EEXIST:
raise
- def prepare_input_files(self):
- """
- Prepares input files for xds, mosflm and hkl2000
- returns: 3 strings
- """
+ def prepare_input_files(self) -> tuple[str | None, str | None, str | None]:
+ """Prepare input files for xds, mosflm and hkl2000."""
return None, None, None
def store_data_collection_in_lims(self):
- """
- Descript. :
- """
+ """Store current data collection information in LIMS database."""
lims = HWR.beamline.lims
- if lims and lims.is_connected() and not self.current_dc_parameters["in_interleave"]:
+ if (
+ lims
+ and lims.is_connected()
+ and not self.current_dc_parameters["in_interleave"]
+ ):
try:
- self.current_dc_parameters[
- "synchrotronMode"
- ] = self.get_machine_fill_mode()
- (collection_id, detector_id,) = HWR.beamline.lims.store_data_collection(
+ self.current_dc_parameters["synchrotronMode"] = (
+ self.get_machine_fill_mode()
+ )
+ (
+ collection_id,
+ detector_id,
+ ) = HWR.beamline.lims.store_data_collection(
self.current_dc_parameters, self.bl_config
)
self.current_dc_parameters["collection_id"] = collection_id
@@ -589,10 +623,8 @@ def store_data_collection_in_lims(self):
"Could not store data collection in LIMS"
)
- def update_data_collection_in_lims(self):
- """
- Descript. :
- """
+ def update_data_collection_in_lims(self) -> None:
+ """Update current data collection information in LIMS database."""
params = self.current_dc_parameters
if HWR.beamline.lims and not params["in_interleave"]:
params["flux"] = HWR.beamline.flux.get_value()
@@ -629,19 +661,30 @@ def update_data_collection_in_lims(self):
)
def store_sample_info_in_lims(self):
- """
- Descript. :
- """
+ """Store current sample information in LIMS database."""
lims = HWR.beamline.lims
- if lims and lims.is_connected() and not self.current_dc_parameters["in_interleave"]:
+ if (
+ lims
+ and lims.is_connected()
+ and not self.current_dc_parameters["in_interleave"]
+ ):
HWR.beamline.lims.update_bl_sample(self.current_lims_sample)
- def store_image_in_lims(self, frame_number, motor_position_id=None):
- """
- Descript. :
+ def store_image_in_lims(
+ self, frame_number: int, motor_position_id: int | None = None
+ ):
+ """Store image information in LIMS database.
+
+ Args:
+ frame_number: Frame number of the image within the data collection.
+ motor_position_id: Motor position ID.
"""
lims = HWR.beamline.lims
- if lims and lims.is_connected() and not self.current_dc_parameters["in_interleave"]:
+ if (
+ lims
+ and lims.is_connected()
+ and not self.current_dc_parameters["in_interleave"]
+ ):
file_location = self.current_dc_parameters["fileinfo"]["directory"]
image_file_template = self.current_dc_parameters["fileinfo"]["template"]
filename = image_file_template % frame_number
@@ -678,31 +721,33 @@ def store_image_in_lims(self, frame_number, motor_position_id=None):
image_id = HWR.beamline.lims.store_image(lims_image)
return image_id
- def update_lims_with_workflow(self, workflow_id, grid_snapshot_filename):
- """Updates collection with information about workflow
+ def update_lims_with_workflow(
+ self, workflow_id: int, grid_snapshot_filename: str
+ ) -> None:
+ """Update collection with information about workflow.
- :param workflow_id: workflow id
- :type workflow_id: int
- :param grid_snapshot_filename: grid snapshot file path
- :type grid_snapshot_filename: string
+ Args:
+ workflow_id: Workflow ID.
+ grid_snapshot_filename: Grid snapshot file path.
"""
lims = HWR.beamline.lims
if lims and lims.is_connected():
try:
self.current_dc_parameters["workflow_id"] = workflow_id
if grid_snapshot_filename:
- self.current_dc_parameters[
- "xtalSnapshotFullPath3"
- ] = grid_snapshot_filename
+ self.current_dc_parameters["xtalSnapshotFullPath3"] = (
+ grid_snapshot_filename
+ )
HWR.beamline.lims.update_data_collection(self.current_dc_parameters)
except BaseException:
logging.getLogger("HWR").exception(
"Could not store data collection into ISPyB"
)
- def get_sample_info(self):
- """
- Descript. :
+ def get_sample_info(self) -> None:
+ """Get current sample information in LIMS database.
+
+ Information is stored in the internal "current_dc_parameters" dictionary.
"""
sample_info = self.current_dc_parameters.get("sample_reference")
try:
@@ -717,9 +762,9 @@ def get_sample_info(self):
pass
elif HWR.beamline.sample_changer:
try:
- self.current_dc_parameters[
- "actualSampleBarcode"
- ] = HWR.beamline.sample_changer.getLoadedSample().getID()
+ self.current_dc_parameters["actualSampleBarcode"] = (
+ HWR.beamline.sample_changer.getLoadedSample().getID()
+ )
self.current_dc_parameters["actualContainerBarcode"] = (
HWR.beamline.sample_changer.getLoadedSample().getContainer().getID()
)
@@ -736,9 +781,11 @@ def get_sample_info(self):
self.current_dc_parameters["actualSampleBarcode"] = None
self.current_dc_parameters["actualContainerBarcode"] = None
- def move_to_centered_position(self):
- """
- Descript. :
+ def move_to_centered_position(self) -> None:
+ """Move motors to the centered position.
+
+ Move motors to the centered position
+ as stored in "current_dc_parameters" dictionary.
"""
positions_str = ""
for motor, position in self.current_dc_parameters["motors"].items():
@@ -752,16 +799,11 @@ def move_to_centered_position(self):
@abc.abstractmethod
@task
- def move_motors(self, motor_position_dict):
- """
- Descript. :
- """
+ def move_motors(self, motor_position_dict) -> None:
return
- def take_crystal_snapshots(self):
- """
- Descript. :
- """
+ def take_crystal_snapshots(self) -> None:
+ """Take crystal snapshots of the currently loaded sample."""
number_of_snapshots = self.current_dc_parameters["take_snapshots"]
if self.current_dc_parameters["experiment_type"] == "Mesh":
number_of_snapshots = 1
@@ -883,16 +925,13 @@ def set_mesh(self, mesh_on):
self.mesh = mesh_on
def set_mesh_scan_parameters(
- self, num_lines, total_nb_frames, mesh_center_param, mesh_range_param
- ):
- """
- sets the mesh scan parameters :
- - vertcal range
- - horizontal range
- - nb lines
- - nb frames per line
- - invert direction (boolean) # NOT YET DONE
- """
+ self,
+ num_lines: int,
+ total_nb_frames: int,
+ mesh_center_param: tuple,
+ mesh_range_param: tuple,
+ ) -> None:
+ """Set the mesh scan parameters."""
return
# self.mesh_num_lines = num_lines
diff --git a/pyproject.toml b/pyproject.toml
index 2fded04c20..098ef752ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -221,7 +221,6 @@ force-exclude = '''
| mxcubecore/HardwareObjects/TangoMotor.py
| mxcubecore/HardwareObjects/abstract/AbstractDetector.py
| mxcubecore/HardwareObjects/abstract/sample_changer/Crims.py
- | mxcubecore/HardwareObjects/abstract/AbstractCollect.py
| mxcubecore/HardwareObjects/mockup/MotorMockup.py
| mxcubecore/HardwareObjects/mockup/ShutterMockup.py
| mxcubecore/Poller.py