import weakref
from typing import Any, TYPE_CHECKING
from serializall import SerializableFactory
from qtpy import QtCore, QtWidgets
from pymodaq.control_modules.enums import MoveType
from pymodaq.utils.managers.modules import ModuleType
from pymodaq.utils.scanner.scanner import Orientation
from pymodaq_data import DataToExport
from pymodaq_gui.parameter.pymodaq_ptypes.itemselect import ItemSelect
from pymodaq_gui.utils.widgets import SpinBox
from pymodaq.extensions.sequencer.utilities.element_factory import SeqEltBase, SeqEltFactory, ElementError
from pymodaq.extensions.sequencer.utilities.elements.repeat import RepeatElt
from pymodaq.extensions.sequencer.utilities.widget_with_toolbar import WidgetWithToolbar
from qt_themes import get_theme
from pymodaq.utils.managers.modules_manager import ModulesManager
from pymodaq.utils.scanner import Scanner
if TYPE_CHECKING:
from pymodaq.control_modules.daq_move import DAQ_Move
ser_factory = SerializableFactory()
[docs]
@SerializableFactory.register_decorator()
@SeqEltFactory.register_elt()
class ScannerElt(SeqEltBase):
elt_name = 'scanner'
children_allowed = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ind_execute = 0
self.scanner = Scanner()
self.scanner_ref: weakref.ref[Scanner] = None
self._actuators_all_to_restore: list[str] = None
self._actuators_selected_to_restore: list[str] = None
def initialize_element(self):
self._ind_execute = 0
def do_things_with_dashboard(self):
if self._actuators_all_to_restore is not None:
act_all_to_restore: list['DAQ_Move'] = []
for act_name in self._actuators_all_to_restore:
if act_name in self.dashboard.modules_manager.actuators_name:
act_all_to_restore.append(
self.dashboard.modules_manager.get_mod_from_name(act_name,
mod=ModuleType.Actuator))
self._actuators_all_to_restore = None
else:
act_all_to_restore = self.dashboard.modules_manager.actuators_all
act_selected_to_restore: list['DAQ_Move'] = []
if self._actuators_selected_to_restore is not None:
for act_name in self._actuators_selected_to_restore:
if act_name in self.dashboard.modules_manager.actuators_name:
act_selected_to_restore.append(
self.dashboard.modules_manager.get_mod_from_name(act_name,
mod=ModuleType.Actuator))
self._actuators_selected_to_restore = None
self.scanner.actuators_all = act_all_to_restore
self.scanner.actuators = act_selected_to_restore
def _create_widget(self, base_widget: WidgetWithToolbar) -> WidgetWithToolbar:
self.scanner_ref = weakref.ref(Scanner(actuators=self.scanner.actuators_all,
selected_actuators=self.scanner.actuators,
orientation=Orientation.HORIZONTAL))
base_widget.insert_widget(self.scanner_ref().parent_widget)
self.scanner_ref().settings.child('actuators').show()
self.scanner_ref().from_dict(self.scanner.to_dict(use_real_actuators=True))
if base_widget.parent() is not None:
base_widget.parent().popup_hiding.connect(self._on_editor_closing)
return base_widget
def _on_editor_closing(self):
if self.scanner_ref is not None and self.scanner_ref() is not None:
self.scanner.from_dict(self.scanner_ref().to_dict(use_real_actuators=True))
self.scanner.scanner.evaluate_steps()
self.scanner.save_scanner_settings()
index_to_update = self.parent_sequence.model.index_from_element(self)
self.parent_sequence.model.dataChanged.emit(index_to_update,
index_to_update,
[QtCore.Qt.ItemDataRole.DisplayRole])
def _execute(self, dte: DataToExport=None):
if self._ind_execute == 0:
self.scanner.set_scan()
if self._ind_execute < self.scanner.n_steps:
next_values = self.scanner.positions_at(self._ind_execute)
self.dashboard.modules_manager.move_actuators_with_callback(
dte_act = next_values,
mode= MoveType.ABS,
callback = self._on_move_done,
do_connect_modules=True)
""" This will execute the children state and its bundled elements n_repeat times"""
self._ind_execute += 1
index_to_update = self.parent_sequence.model.index_from_element(self)
self.parent_sequence.model.dataChanged.emit(index_to_update,
index_to_update,
[QtCore.Qt.ItemDataRole.DisplayRole])
else:
self._ind_execute = 0
self.done_signal.emit()
def _on_move_done(self, dte: DataToExport):
""" Called once the current step is done
Parameters
----------
dte: DataToExport
Contains the actuators dwa at the moment when the move has been set as done
(may be slightly different from the actual target dwa due to epsilon)
"""
self.dashboard.modules_manager.forget_callback(self._on_move_done,
module_type = ModuleType.Actuator,
disconnect_modules = True
)
self.save_data(self.scanner.positions_at(self._ind_execute), done=False)
self.children_signal.emit() # triggers the execution of this elt's first child
[docs]
def to_dict_custom(self) -> dict[str, Any]:
""" adds attribute to a dict in order to produce a human readable
representation/configuration for this element
to be reimplemented
"""
return self.scanner.to_dict()
[docs]
def from_dict_custom(self, dict_config: dict[str, Any]):
""" Create/set the custom part of the element to finish initialization
using setters, attribute assignment or methods
"""
#loading from file implies actuators is a list of str but at the moment of restoring the elt, the dashboard has
# not been set yet and self.scanner has also no actuators yet...
if len(dict_config['actuators']) > 0 and isinstance(dict_config['actuators'][0], str):
self._actuators_all_to_restore = dict_config['actuators']
dict_config['actuators'] = [] # will be restored later, when dashboard is set
if len(dict_config['selected']) > 0 and isinstance(dict_config['selected'][0], str):
self._actuators_selected_to_restore = dict_config['selected']
dict_config['selected'] = [] # will be restored later, when dashboard is set
self.scanner.from_dict(dict_config)
def _eq(self, other: 'ScannerElt'):
""" Custom method to reimplement to assert two elements are equals"""
return self.scanner.to_dict() == other.scanner.to_dict()
def __repr__(self):
return f'{super().__repr__()} - {self.scanner} - Step:{self._ind_execute}'
[docs]
def check_set_is_valid(self):
""" Check the validity of the element
Will be called before executing the element. Try to make sure the element is valid or return None
if the user may do something!
"""
self.scanner.set_scan()
if not (self.scanner.n_steps >= 1):
raise ElementError(f'Element {self}: at least one scan step required')
def size_hint(self) -> QtCore.QSize:
return QtCore.QSize(200, 500)