From bb802981856deb97564f33f4af7a4070878f28bf Mon Sep 17 00:00:00 2001 From: fabcor Date: Thu, 30 Jan 2025 14:48:55 +0100 Subject: [PATCH] Improve docstrings for abstract hardware objects General improvements to the docstrings of abstract hardware objects: * Add an `Emits` custom section for Sphinx's Napoleon extension * Fix docstrings formatting * Document some emitted signals * Move types from docstring to type hints when possible * Som formatting with `black` GitHub: supersedes https://github.com/mxcube/mxcubecore/pull/866 GitHub: supersedes https://github.com/mxcube/mxcubecore/pull/947 --- docs/conf.py | 10 +- docs/source/dev/docs.md | 21 + docs/source/dev/hwobj_signals.md | 415 ++++++++++++++++++ .../abstract/AbstractActuator.py | 114 +++-- .../abstract/AbstractAperture.py | 104 ++--- .../HardwareObjects/abstract/AbstractBeam.py | 159 ++++--- .../abstract/AbstractCharacterisation.py | 162 +++---- .../abstract/AbstractCollect.py | 365 ++++++++------- pyproject.toml | 1 - 9 files changed, 947 insertions(+), 404 deletions(-) create mode 100644 docs/source/dev/hwobj_signals.md 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