pymodaq 5.0.5__py3-none-any.whl → 5.1.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymodaq might be problematic. Click here for more details.
- pymodaq/control_modules/daq_move.py +77 -64
- pymodaq/control_modules/daq_move_ui.py +16 -15
- pymodaq/control_modules/daq_viewer.py +95 -87
- pymodaq/control_modules/daq_viewer_ui.py +22 -23
- pymodaq/control_modules/mocks.py +2 -2
- pymodaq/control_modules/move_utility_classes.py +28 -19
- pymodaq/control_modules/thread_commands.py +138 -0
- pymodaq/control_modules/utils.py +88 -20
- pymodaq/control_modules/viewer_utility_classes.py +8 -17
- pymodaq/dashboard.py +90 -27
- pymodaq/examples/qt_less_standalone_module.py +48 -11
- pymodaq/extensions/__init__.py +7 -3
- pymodaq/extensions/adaptive/__init__.py +2 -0
- pymodaq/extensions/adaptive/adaptive_optimization.py +159 -0
- pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
- pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +86 -0
- pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
- pymodaq/extensions/adaptive/loss_function/loss_factory.py +106 -0
- pymodaq/extensions/adaptive/utils.py +97 -0
- pymodaq/extensions/bayesian/__init__.py +1 -1
- pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
- pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +71 -0
- pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +86 -0
- pymodaq/extensions/bayesian/bayesian_optimization.py +121 -0
- pymodaq/extensions/bayesian/utils.py +27 -286
- pymodaq/extensions/daq_logger/daq_logger.py +7 -12
- pymodaq/extensions/daq_logger/h5logging.py +1 -1
- pymodaq/extensions/daq_scan.py +18 -47
- pymodaq/extensions/h5browser.py +3 -34
- pymodaq/extensions/optimizers_base/__init__.py +0 -0
- pymodaq/extensions/{bayesian/bayesian_optimisation.py → optimizers_base/optimizer.py} +441 -334
- pymodaq/extensions/optimizers_base/thread_commands.py +20 -0
- pymodaq/extensions/optimizers_base/utils.py +378 -0
- pymodaq/extensions/pid/pid_controller.py +6 -10
- pymodaq/extensions/utils.py +12 -0
- pymodaq/utils/data.py +1 -0
- pymodaq/utils/gui_utils/loader_utils.py +2 -0
- pymodaq/utils/h5modules/module_saving.py +134 -22
- pymodaq/utils/leco/daq_move_LECODirector.py +73 -73
- pymodaq/utils/leco/daq_xDviewer_LECODirector.py +36 -84
- pymodaq/utils/leco/director_utils.py +25 -10
- pymodaq/utils/leco/leco_director.py +65 -26
- pymodaq/utils/leco/pymodaq_listener.py +118 -68
- pymodaq/utils/leco/utils.py +24 -24
- pymodaq/utils/managers/modules_manager.py +37 -8
- pymodaq/utils/scanner/scanners/_1d_scanners.py +0 -38
- pymodaq/utils/scanner/scanners/_2d_scanners.py +0 -58
- {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/METADATA +4 -3
- {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/RECORD +52 -38
- {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/entry_points.txt +0 -2
- pymodaq/utils/leco/desktop.ini +0 -2
- {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/WHEEL +0 -0
- {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/licenses/LICENSE +0 -0
pymodaq/dashboard.py
CHANGED
|
@@ -8,7 +8,7 @@ import logging
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from importlib import import_module
|
|
10
10
|
from packaging import version as version_mod
|
|
11
|
-
from typing import Tuple, List, Any, TYPE_CHECKING
|
|
11
|
+
from typing import Tuple, List, Any, TYPE_CHECKING, Sequence
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
from qtpy import QtGui, QtWidgets, QtCore
|
|
@@ -46,7 +46,6 @@ from pymodaq.utils import config as config_mod_pymodaq
|
|
|
46
46
|
from pymodaq.control_modules.daq_move import DAQ_Move
|
|
47
47
|
from pymodaq.control_modules.daq_viewer import DAQ_Viewer
|
|
48
48
|
from pymodaq_gui.utils.splash import get_splash_sc
|
|
49
|
-
|
|
50
49
|
from pymodaq import extensions as extmod
|
|
51
50
|
|
|
52
51
|
logger = set_logger(get_module_name(__file__))
|
|
@@ -72,6 +71,7 @@ class ManagerEnums(BaseEnum):
|
|
|
72
71
|
overshoot = 2
|
|
73
72
|
roi = 3
|
|
74
73
|
|
|
74
|
+
|
|
75
75
|
class PymodaqUpdateTableWidget(QTableWidget):
|
|
76
76
|
'''
|
|
77
77
|
A class to represent PyMoDAQ and its subpackages'
|
|
@@ -113,10 +113,9 @@ class PymodaqUpdateTableWidget(QTableWidget):
|
|
|
113
113
|
self.setItem(row, 2, QTableWidgetItem(str(current_version)))
|
|
114
114
|
self.setItem(row, 3, QTableWidgetItem(str(available_version)))
|
|
115
115
|
|
|
116
|
-
|
|
117
116
|
def get_checked_data(self):
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
checked = list(map(lambda c : c.isChecked(), self._checkboxes))
|
|
118
|
+
return list(np.array(self._package_versions)[checked])
|
|
120
119
|
|
|
121
120
|
def sizeHint(self):
|
|
122
121
|
self.resizeColumnsToContents()
|
|
@@ -133,6 +132,7 @@ class PymodaqUpdateTableWidget(QTableWidget):
|
|
|
133
132
|
|
|
134
133
|
return QSize(width, height)
|
|
135
134
|
|
|
135
|
+
|
|
136
136
|
class DashBoard(CustomApp):
|
|
137
137
|
"""
|
|
138
138
|
Main class initializing a DashBoard interface to display det and move modules and logger """
|
|
@@ -336,9 +336,40 @@ class DashBoard(CustomApp):
|
|
|
336
336
|
self.bayesian_window.setWindowTitle('Bayesian Optimiser')
|
|
337
337
|
self.bayesian_module = extmod.BayesianOptimisation(dockarea=dockarea, dashboard=self)
|
|
338
338
|
self.extensions['bayesian'] = self.bayesian_module
|
|
339
|
-
|
|
339
|
+
|
|
340
|
+
if self.bayesian_module.validate_config():
|
|
341
|
+
self.bayesian_window.show()
|
|
342
|
+
else:
|
|
343
|
+
messagebox(severity='critical', title="Bayesian Optimisation error",
|
|
344
|
+
text=f"""
|
|
345
|
+
<p>Saved Bayesian Optimisation configuration file is not compatible anymore.</p>
|
|
346
|
+
<p>Please delete the file at <b>{self.bayesian_module.config_path}</b>.</p>
|
|
347
|
+
""")
|
|
348
|
+
self.bayesian_module.quit()
|
|
340
349
|
return self.bayesian_module
|
|
341
350
|
|
|
351
|
+
def load_adaptive(self, win=None):
|
|
352
|
+
if win is None:
|
|
353
|
+
self.adaptive_window = QtWidgets.QMainWindow()
|
|
354
|
+
else:
|
|
355
|
+
self.adaptive_window = win
|
|
356
|
+
dockarea = DockArea()
|
|
357
|
+
self.adaptive_window.setCentralWidget(dockarea)
|
|
358
|
+
self.adaptive_window.setWindowTitle('Adaptive Scan')
|
|
359
|
+
self.adaptive_module = extmod.AdaptiveOptimisation(dockarea=dockarea, dashboard=self)
|
|
360
|
+
self.extensions['adaptive'] = self.adaptive_module
|
|
361
|
+
|
|
362
|
+
if self.adaptive_module.validate_config():
|
|
363
|
+
self.adaptive_window.show()
|
|
364
|
+
else:
|
|
365
|
+
messagebox(severity='critical', title="Adaptive Optimisation error",
|
|
366
|
+
text=f"""
|
|
367
|
+
<p>Saved Adaptive Optimisation configuration file is not compatible anymore.</p>
|
|
368
|
+
<p>Please delete the file at <b>{self.adaptive_module.config_path}</b>.</p>
|
|
369
|
+
""")
|
|
370
|
+
self.adaptive_module.quit()
|
|
371
|
+
return self.adaptive_module
|
|
372
|
+
|
|
342
373
|
def load_extension_from_name(self, name: str) -> dict:
|
|
343
374
|
return self.load_extensions_module(find_dict_in_list_from_key_val(extensions, 'name', name))
|
|
344
375
|
|
|
@@ -440,6 +471,7 @@ class DashBoard(CustomApp):
|
|
|
440
471
|
self.add_action('do_pid', 'PID module', auto_toolbar=False)
|
|
441
472
|
self.add_action('console', 'IPython Console', auto_toolbar=False)
|
|
442
473
|
self.add_action('bayesian', 'Bayesian Optimisation', auto_toolbar=False)
|
|
474
|
+
self.add_action('adaptive', 'Adaptive Scan', auto_toolbar=False)
|
|
443
475
|
|
|
444
476
|
self.add_action('about', 'About', 'information2')
|
|
445
477
|
self.add_action('help', 'Help', 'help1')
|
|
@@ -517,6 +549,8 @@ class DashBoard(CustomApp):
|
|
|
517
549
|
self.connect_action('do_pid', lambda: self.load_pid_module())
|
|
518
550
|
self.connect_action('console', lambda: self.load_console())
|
|
519
551
|
self.connect_action('bayesian', lambda: self.load_bayesian())
|
|
552
|
+
self.connect_action('adaptive', lambda: self.load_adaptive())
|
|
553
|
+
|
|
520
554
|
|
|
521
555
|
self.connect_action('about', self.show_about)
|
|
522
556
|
self.connect_action('help', self.show_help)
|
|
@@ -598,6 +632,7 @@ class DashBoard(CustomApp):
|
|
|
598
632
|
self.extensions_menu.addAction(self.get_action('do_pid'))
|
|
599
633
|
self.extensions_menu.addAction(self.get_action('console'))
|
|
600
634
|
self.extensions_menu.addAction(self.get_action('bayesian'))
|
|
635
|
+
self.extensions_menu.addAction(self.get_action('adaptive'))
|
|
601
636
|
|
|
602
637
|
# extensions from plugins
|
|
603
638
|
extensions_actions = []
|
|
@@ -613,13 +648,15 @@ class DashBoard(CustomApp):
|
|
|
613
648
|
help_menu.addAction(self.get_action('check_update'))
|
|
614
649
|
help_menu.addAction(self.get_action('plugin_manager'))
|
|
615
650
|
|
|
616
|
-
self.
|
|
617
|
-
|
|
618
|
-
self.
|
|
619
|
-
self.
|
|
620
|
-
self.
|
|
621
|
-
self.
|
|
622
|
-
self.
|
|
651
|
+
status = self.preset_file is None
|
|
652
|
+
|
|
653
|
+
self.overshoot_menu.setEnabled(not status)
|
|
654
|
+
self.roi_menu.setEnabled(not status)
|
|
655
|
+
self.remote_menu.setEnabled(not status)
|
|
656
|
+
self.extensions_menu.setEnabled(not status)
|
|
657
|
+
self.file_menu.setEnabled(status)
|
|
658
|
+
self.settings_menu.setEnabled(status)
|
|
659
|
+
self.preset_menu.setEnabled(status)
|
|
623
660
|
|
|
624
661
|
def start_plugin_manager(self):
|
|
625
662
|
self.win_plug_manager = QtWidgets.QMainWindow()
|
|
@@ -955,13 +992,33 @@ class DashBoard(CustomApp):
|
|
|
955
992
|
detector_modules.append(det_mod_tmp)
|
|
956
993
|
return det_mod_tmp
|
|
957
994
|
|
|
995
|
+
def override_det_from_extension(self, overriden_grabbers: Sequence[str] = None):
|
|
996
|
+
""" (Experimental) If an extension adding detectors within the Dashboard need to, it could call this
|
|
997
|
+
method.
|
|
998
|
+
|
|
999
|
+
Then if some other extension trigger a grab from it, the request of a grab won't be done twice
|
|
1000
|
+
|
|
1001
|
+
Parameters
|
|
1002
|
+
----------
|
|
1003
|
+
overriden_grabbers: Sequence[str]
|
|
1004
|
+
sequence of detector names whose corresponding modules should set their
|
|
1005
|
+
attribute override_grab_from_extension to True.
|
|
1006
|
+
"""
|
|
1007
|
+
if overriden_grabbers is not None:
|
|
1008
|
+
for mod_name in overriden_grabbers:
|
|
1009
|
+
mod = self.modules_manager.get_mod_from_name(mod_name, 'det')
|
|
1010
|
+
if mod is not None:
|
|
1011
|
+
mod.override_grab_from_extension = True
|
|
1012
|
+
|
|
958
1013
|
def add_det_from_extension(self, name: str, daq_type: str, instrument_name: str,
|
|
959
1014
|
instrument_controller: Any):
|
|
1015
|
+
|
|
960
1016
|
""" Specific method to add a DAQ_Viewer within the Dashboard. This Particular detector
|
|
961
1017
|
should be defined in the plugin of the extension and is used to mimic a grab while data
|
|
962
1018
|
are actually coming from the extension which loaded it
|
|
963
1019
|
|
|
964
1020
|
For an exemple, see the pymodaq_plugins_datamixer plugin and its DataMixer extension
|
|
1021
|
+
or the DAQ_PID extension
|
|
965
1022
|
|
|
966
1023
|
Parameters
|
|
967
1024
|
----------
|
|
@@ -992,7 +1049,8 @@ class DashBoard(CustomApp):
|
|
|
992
1049
|
|
|
993
1050
|
def update_module_manager(self):
|
|
994
1051
|
if self.modules_manager is None:
|
|
995
|
-
self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules
|
|
1052
|
+
self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules,
|
|
1053
|
+
parent_name='Dashboard')
|
|
996
1054
|
else:
|
|
997
1055
|
self.modules_manager.actuators_all = self.actuators_modules
|
|
998
1056
|
self.modules_manager.detectors_all = self.detector_modules
|
|
@@ -1033,17 +1091,20 @@ class DashBoard(CustomApp):
|
|
|
1033
1091
|
self.preset_manager.preset_params.child('Moves').children()]
|
|
1034
1092
|
plugins += [{'type': 'det', 'value': child} for child in
|
|
1035
1093
|
self.preset_manager.preset_params.child('Detectors').children()]
|
|
1036
|
-
|
|
1037
1094
|
for plug in plugins:
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
'params', 'move_settings',
|
|
1095
|
+
if plug["type"] == 'det':
|
|
1096
|
+
try:
|
|
1097
|
+
plug['ID'] = plug['value']['params', 'detector_settings', 'controller_ID']
|
|
1098
|
+
plug['status'] = plug['value']['params', 'detector_settings', 'controller_status']
|
|
1099
|
+
except KeyError as e:
|
|
1100
|
+
raise DetectorError
|
|
1101
|
+
else:
|
|
1102
|
+
try:
|
|
1103
|
+
plug['ID'] = plug['value']['params', 'move_settings','multiaxes', 'controller_ID']
|
|
1104
|
+
plug['status'] = plug['value']['params', 'move_settings', 'multiaxes', 'multi_status']
|
|
1105
|
+
except KeyError as e:
|
|
1106
|
+
raise ActuatorError
|
|
1107
|
+
|
|
1047
1108
|
|
|
1048
1109
|
|
|
1049
1110
|
IDs = list(set([plug['ID'] for plug in plugins]))
|
|
@@ -1421,8 +1482,11 @@ class DashBoard(CustomApp):
|
|
|
1421
1482
|
self.mainwindow.setVisible(True)
|
|
1422
1483
|
for area in self.dockarea.tempAreas:
|
|
1423
1484
|
area.window().setVisible(True)
|
|
1424
|
-
messagebox(
|
|
1425
|
-
|
|
1485
|
+
messagebox(severity='critical', title="Preset loading error",
|
|
1486
|
+
text=f"""
|
|
1487
|
+
<p>Saved preset file is not compatible anymore.</p>
|
|
1488
|
+
<p>Please recreate the preset at <b>{filename}</b>.</p>
|
|
1489
|
+
""")
|
|
1426
1490
|
logger.exception(str(error))
|
|
1427
1491
|
|
|
1428
1492
|
self.quit_fun()
|
|
@@ -1607,7 +1671,6 @@ class DashBoard(CustomApp):
|
|
|
1607
1671
|
|
|
1608
1672
|
packages_data = np.array(list(zip(packages, current_versions, available_versions)))[new_versions]
|
|
1609
1673
|
|
|
1610
|
-
#TODO: Remove `or True`
|
|
1611
1674
|
if len(packages_data) > 0:
|
|
1612
1675
|
#Create a QDialog window and different graphical components
|
|
1613
1676
|
dialog = QtWidgets.QDialog()
|
|
@@ -13,11 +13,14 @@ For remote control, you need to start a Coordinator, as described for remote con
|
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
15
|
from time import sleep
|
|
16
|
-
from typing import List,
|
|
16
|
+
from typing import cast, List, Optional
|
|
17
17
|
|
|
18
18
|
from pyleco.utils.listener import Listener
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
from pymodaq_data.data import DataWithAxes
|
|
22
|
+
from pymodaq_utils.serialize.factory import SerializableFactory
|
|
23
|
+
|
|
21
24
|
class QtLessModule:
|
|
22
25
|
"""Some module doing things without Qt.
|
|
23
26
|
|
|
@@ -33,6 +36,9 @@ class QtLessModule:
|
|
|
33
36
|
self._fake_position = 0
|
|
34
37
|
self.start_listen()
|
|
35
38
|
self._stored = []
|
|
39
|
+
# register DataWithAxes for deserialization
|
|
40
|
+
cls = DataWithAxes
|
|
41
|
+
SerializableFactory().register_from_type(cls, cls.serialize, cls.deserialize)
|
|
36
42
|
|
|
37
43
|
def start_listen(self) -> None:
|
|
38
44
|
"""Start to listen on incoming commands."""
|
|
@@ -45,8 +51,9 @@ class QtLessModule:
|
|
|
45
51
|
register_rpc_method = self.communicator.register_rpc_method
|
|
46
52
|
register_rpc_method(self.set_info)
|
|
47
53
|
register_rpc_method(self.send_data)
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
# binary methods can accept additionally binary payload, like serialized pymodaq objects.
|
|
55
|
+
self.listener.register_binary_rpc_method(self.move_abs, accept_binary_input=True)
|
|
56
|
+
self.listener.register_binary_rpc_method(self.move_rel, accept_binary_input=True)
|
|
50
57
|
register_rpc_method(self.move_home)
|
|
51
58
|
register_rpc_method(self.get_actuator_value)
|
|
52
59
|
register_rpc_method(self.stop_motion)
|
|
@@ -56,7 +63,17 @@ class QtLessModule:
|
|
|
56
63
|
"""Stop to listen on incoming commands."""
|
|
57
64
|
self.listener.stop_listen()
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
@staticmethod
|
|
67
|
+
def extract_pymodaq_object(
|
|
68
|
+
value: Optional[float], additional_payload: Optional[List[bytes]]
|
|
69
|
+
):
|
|
70
|
+
if value is None and additional_payload:
|
|
71
|
+
res = cast(DataWithAxes, SerializableFactory().get_apply_deserializer(additional_payload[0]))
|
|
72
|
+
else:
|
|
73
|
+
res = value
|
|
74
|
+
return res
|
|
75
|
+
|
|
76
|
+
# methods for being remote controlled
|
|
60
77
|
# these methods are executed and cannot talk to the controlling module directly.
|
|
61
78
|
# if you need to send a response (for example with a value) you have to store the information and
|
|
62
79
|
# send it after these methods have been executed.
|
|
@@ -73,13 +90,33 @@ class QtLessModule:
|
|
|
73
90
|
print("send_data")
|
|
74
91
|
|
|
75
92
|
# actuator commands
|
|
76
|
-
def move_abs(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
def move_abs(
|
|
94
|
+
self,
|
|
95
|
+
position: Optional[float],
|
|
96
|
+
additional_payload: Optional[List[bytes]] = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Move to an absolute position.
|
|
99
|
+
|
|
100
|
+
:param position: Deprecated, should be None and content transferred binary.
|
|
101
|
+
:param additional_payload: binary frames containing the position as PyMoDAQ `DataActuator`.
|
|
102
|
+
"""
|
|
103
|
+
pos = self.extract_pymodaq_object(position, additional_payload)
|
|
104
|
+
print("move_abs", pos)
|
|
105
|
+
self._fake_position = float(pos)
|
|
106
|
+
|
|
107
|
+
def move_rel(
|
|
108
|
+
self,
|
|
109
|
+
position: Optional[float],
|
|
110
|
+
additional_payload: Optional[List[bytes]] = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Move by a relative position.
|
|
113
|
+
|
|
114
|
+
:param position: Deprecated, should be None and content transferred binary.
|
|
115
|
+
:param additional_payload: binary frames containing the position as PyMoDAQ `DataActuator`.
|
|
116
|
+
"""
|
|
117
|
+
pos = self.extract_pymodaq_object(position, additional_payload)
|
|
118
|
+
print("move_rel", pos)
|
|
119
|
+
self._fake_position += float(pos)
|
|
83
120
|
|
|
84
121
|
def move_home(self) -> None:
|
|
85
122
|
self._fake_position = 0
|
pymodaq/extensions/__init__.py
CHANGED
|
@@ -11,10 +11,14 @@ from .console import QtConsole
|
|
|
11
11
|
from .daq_scan import DAQScan
|
|
12
12
|
from .daq_logger.daq_logger import DAQ_Logger
|
|
13
13
|
from .pid.pid_controller import DAQ_PID
|
|
14
|
-
from .h5browser import H5Browser
|
|
14
|
+
from .h5browser import H5Browser #backcompat but should be loaded from pymodaq_gui!
|
|
15
|
+
|
|
16
|
+
from .bayesian.bayesian_optimization import BayesianOptimization
|
|
17
|
+
from .bayesian.utils import OptimizerModelDefault
|
|
18
|
+
|
|
19
|
+
from .adaptive.adaptive_optimization import AdaptiveOptimisation
|
|
20
|
+
|
|
15
21
|
|
|
16
|
-
from .bayesian.bayesian_optimisation import BayesianOptimisation
|
|
17
|
-
from .bayesian.utils import BayesianModelDefault, BayesianModelGeneric
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
from pymodaq_utils import utils
|
|
3
|
+
from pymodaq_utils import config as config_mod
|
|
4
|
+
from pymodaq_utils.logger import set_logger, get_module_name
|
|
5
|
+
from pymodaq_utils.utils import ThreadCommand
|
|
6
|
+
|
|
7
|
+
from pymodaq.extensions.optimizers_base.optimizer import (
|
|
8
|
+
GenericOptimization, OptimizationRunner, optimizer_params)
|
|
9
|
+
from pymodaq.extensions.optimizers_base.utils import OptimizerModelDefault, find_key_in_nested_dict
|
|
10
|
+
from pymodaq.extensions.optimizers_base.thread_commands import OptimizerToRunner
|
|
11
|
+
|
|
12
|
+
from pymodaq.extensions.adaptive.loss_function import LossFunctionFactory,LossDim
|
|
13
|
+
from pymodaq.extensions.adaptive.utils import AdaptiveAlgorithm, AdaptiveConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = set_logger(get_module_name(__file__))
|
|
17
|
+
config = config_mod.Config()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
EXTENSION_NAME = 'AdaptiveScan'
|
|
21
|
+
CLASS_NAME = 'AdaptiveOptimization'
|
|
22
|
+
|
|
23
|
+
STARTING_LOSS_DIM = LossDim.LOSS_1D
|
|
24
|
+
|
|
25
|
+
PREDICTION_NAMES = list(LossFunctionFactory.keys(STARTING_LOSS_DIM))
|
|
26
|
+
PREDICTION_PARAMS = (
|
|
27
|
+
[{'title': 'LossDim', 'name': 'lossdim', 'type': 'str',
|
|
28
|
+
'value': LossDim.LOSS_1D, 'readonly': True},
|
|
29
|
+
{'title': 'Kind', 'name': 'kind', 'type': 'list',
|
|
30
|
+
'value': PREDICTION_NAMES[0],
|
|
31
|
+
'limits': PREDICTION_NAMES}] +
|
|
32
|
+
LossFunctionFactory.get(STARTING_LOSS_DIM,
|
|
33
|
+
PREDICTION_NAMES[0]).params)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AdaptiveOptimizationRunner(OptimizationRunner):
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
def queue_command(self, command: ThreadCommand):
|
|
43
|
+
"""
|
|
44
|
+
"""
|
|
45
|
+
if command.command == OptimizerToRunner.PREDICTION:
|
|
46
|
+
utility_params = {k: v for k, v in command.attribute.items()
|
|
47
|
+
if k not in ("kind", "tradeoff_actual", 'lossdim')}
|
|
48
|
+
|
|
49
|
+
self.optimization_algorithm.set_prediction_function(command.attribute['lossdim'],
|
|
50
|
+
command.attribute['kind'],
|
|
51
|
+
**utility_params)
|
|
52
|
+
else:
|
|
53
|
+
super().queue_command(command)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AdaptiveOptimisation(GenericOptimization):
|
|
57
|
+
""" PyMoDAQ extension of the DashBoard to perform the optimization of a target signal
|
|
58
|
+
taken form the detectors as a function of one or more parameters controlled by the actuators.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
runner = AdaptiveOptimizationRunner
|
|
62
|
+
params = optimizer_params(PREDICTION_PARAMS)
|
|
63
|
+
config_saver = AdaptiveConfig
|
|
64
|
+
|
|
65
|
+
DISPLAY_BEST = False
|
|
66
|
+
|
|
67
|
+
def ini_custom_attributes(self):
|
|
68
|
+
""" Here you can reimplement specific attributes"""
|
|
69
|
+
self._base_name: str = 'Adaptive'
|
|
70
|
+
self.settings.child('main_settings', 'ini_random').hide()
|
|
71
|
+
|
|
72
|
+
def validate_config(self) -> bool:
|
|
73
|
+
utility = find_key_in_nested_dict(self.optimizer_config.to_dict(), 'prediction')
|
|
74
|
+
if utility:
|
|
75
|
+
try:
|
|
76
|
+
utility_params = { k : v for k, v in utility.items() \
|
|
77
|
+
if k not in ("kind", "tradeoff_actual", 'lossdim') }
|
|
78
|
+
LossFunctionFactory.create(utility['lossdim'],
|
|
79
|
+
utility['kind'], **utility_params)
|
|
80
|
+
except (ValueError, KeyError):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def value_changed(self, param):
|
|
86
|
+
""" to be subclassed for actions to perform when one of the param's value in self.settings is changed
|
|
87
|
+
|
|
88
|
+
For instance:
|
|
89
|
+
if param.name() == 'do_something':
|
|
90
|
+
if param.value():
|
|
91
|
+
print('Do something')
|
|
92
|
+
self.settings.child('main_settings', 'something_done').setValue(False)
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
param: (Parameter) the parameter whose value just changed
|
|
97
|
+
"""
|
|
98
|
+
super().value_changed(param)
|
|
99
|
+
if param.name() == 'lossdim':
|
|
100
|
+
self.settings.child('main_settings', 'prediction', 'kind').setLimits(
|
|
101
|
+
LossFunctionFactory.keys(param.value())
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
elif param.name() == 'kind':
|
|
105
|
+
utility_settings = self.settings.child('main_settings', 'prediction')
|
|
106
|
+
old_children = utility_settings.children()[2:]
|
|
107
|
+
for child in old_children:
|
|
108
|
+
utility_settings.removeChild(child)
|
|
109
|
+
try:
|
|
110
|
+
params = LossFunctionFactory.get(utility_settings['lossdim'],
|
|
111
|
+
param.value()).params
|
|
112
|
+
utility_settings.addChildren(params)
|
|
113
|
+
except (KeyError, ValueError):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
def update_prediction_function(self):
|
|
117
|
+
utility_settings = self.settings.child('main_settings', 'prediction')
|
|
118
|
+
try:
|
|
119
|
+
uparams = {child.name() : child.value() for child in utility_settings.children()}
|
|
120
|
+
LossFunctionFactory.get(uparams['lossdim'], uparams['kind'])
|
|
121
|
+
self.command_runner.emit(
|
|
122
|
+
utils.ThreadCommand(OptimizerToRunner.PREDICTION, uparams))
|
|
123
|
+
except (KeyError, ValueError):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
def update_after_actuators_changed(self, actuators: list[str]):
|
|
127
|
+
""" Actions to do after the actuators have been updated
|
|
128
|
+
"""
|
|
129
|
+
self.settings.child('main_settings', 'prediction',
|
|
130
|
+
'lossdim').setValue(LossDim.get_enum_from_dim_as_int(len(actuators)))
|
|
131
|
+
self.update_prediction_function()
|
|
132
|
+
|
|
133
|
+
def adaptive_bounds(self):
|
|
134
|
+
return list(self.format_bounds().values())
|
|
135
|
+
|
|
136
|
+
def set_algorithm(self):
|
|
137
|
+
self.algorithm = AdaptiveAlgorithm(
|
|
138
|
+
ini_random=1,
|
|
139
|
+
bounds=self.adaptive_bounds(),
|
|
140
|
+
loss_type=LossDim(self.settings['main_settings', 'prediction', 'lossdim']),
|
|
141
|
+
kind=self.settings['main_settings', 'prediction', 'kind'])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
from pymodaq_gui.utils.utils import mkQApp
|
|
146
|
+
from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
|
|
147
|
+
|
|
148
|
+
app = mkQApp('Adaptive Optimiser')
|
|
149
|
+
preset_file_name = config('presets', f'default_preset_for_scan')
|
|
150
|
+
|
|
151
|
+
dashboard, extension, win = load_dashboard_with_preset(preset_file_name, 'AdaptiveScan')
|
|
152
|
+
|
|
153
|
+
app.exec()
|
|
154
|
+
|
|
155
|
+
return dashboard, extension, win
|
|
156
|
+
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
main()
|
|
159
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Callable
|
|
2
|
+
|
|
3
|
+
from .loss_factory import LossFunctionBase, LossFunctionFactory, LossDim
|
|
4
|
+
|
|
5
|
+
from adaptive.learner.learner1D import (
|
|
6
|
+
curvature_loss_function,
|
|
7
|
+
default_loss,
|
|
8
|
+
uniform_loss,
|
|
9
|
+
resolution_loss_function,
|
|
10
|
+
abs_min_log_loss,
|
|
11
|
+
uses_nth_neighbors,
|
|
12
|
+
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def default_loss_function(*args, **kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
17
|
+
return default_loss
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def uniform_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
21
|
+
return uniform_loss
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def abs_min_log_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
25
|
+
return abs_min_log_loss
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@LossFunctionFactory.register()
|
|
30
|
+
class DefaultLoss(LossFunctionBase):
|
|
31
|
+
_loss = staticmethod(default_loss_function)
|
|
32
|
+
dim = LossDim.LOSS_1D
|
|
33
|
+
usual_name = 'Default'
|
|
34
|
+
params = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@LossFunctionFactory.register()
|
|
38
|
+
class UniformLoss(LossFunctionBase):
|
|
39
|
+
_loss = staticmethod(uniform_loss_function)
|
|
40
|
+
dim = LossDim.LOSS_1D
|
|
41
|
+
usual_name = 'Uniform'
|
|
42
|
+
params = []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@LossFunctionFactory.register()
|
|
46
|
+
class CurvatureLoss(LossFunctionBase):
|
|
47
|
+
_loss = staticmethod(curvature_loss_function)
|
|
48
|
+
dim = LossDim.LOSS_1D
|
|
49
|
+
usual_name = 'Curvature'
|
|
50
|
+
params = [
|
|
51
|
+
{'title': 'Area', 'name': 'area_factor', 'type': 'float', 'value': 1.},
|
|
52
|
+
{'title': 'Euclid', 'name': 'euclid_factor', 'type': 'float', 'value': 0.02},
|
|
53
|
+
{'title': 'Horizontal', 'name': 'horizontal_factor', 'type': 'float', 'value': 0.02}
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@LossFunctionFactory.register()
|
|
58
|
+
class ResolutionLoss(LossFunctionBase):
|
|
59
|
+
_loss = staticmethod(resolution_loss_function)
|
|
60
|
+
dim = LossDim.LOSS_1D
|
|
61
|
+
usual_name = 'Resolution'
|
|
62
|
+
params = [
|
|
63
|
+
{'title': 'Min:', 'name': 'min_length', 'type': 'float', 'value': 0., 'min': 0., 'max': 1.},
|
|
64
|
+
{'title': 'Max:', 'name': 'max_length', 'type': 'float', 'value': 1., 'min': 0., 'max': 1.},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@LossFunctionFactory.register()
|
|
69
|
+
class AbsMinLogLoss(LossFunctionBase):
|
|
70
|
+
_loss = staticmethod(abs_min_log_loss_function)
|
|
71
|
+
dim = LossDim.LOSS_1D
|
|
72
|
+
usual_name = 'AbsMinLog'
|
|
73
|
+
params = []
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Callable
|
|
2
|
+
|
|
3
|
+
from .loss_factory import LossFunctionBase, LossFunctionFactory, LossDim
|
|
4
|
+
|
|
5
|
+
from adaptive.learner.learner2D import (
|
|
6
|
+
default_loss,
|
|
7
|
+
uniform_loss,
|
|
8
|
+
resolution_loss_function,
|
|
9
|
+
minimize_triangle_surface_loss,
|
|
10
|
+
thresholded_loss_function,
|
|
11
|
+
triangle_loss,
|
|
12
|
+
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def default_loss_function(*args, **kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
17
|
+
return default_loss
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def uniform_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
21
|
+
return uniform_loss
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def triangle_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
25
|
+
return triangle_loss
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def minimize_triangle_surface_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
|
|
29
|
+
return minimize_triangle_surface_loss
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@LossFunctionFactory.register()
|
|
33
|
+
class DefaultLoss(LossFunctionBase):
|
|
34
|
+
_loss = staticmethod(default_loss_function)
|
|
35
|
+
dim = LossDim.LOSS_2D
|
|
36
|
+
usual_name = 'Default'
|
|
37
|
+
params = []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@LossFunctionFactory.register()
|
|
41
|
+
class UniformLoss(LossFunctionBase):
|
|
42
|
+
_loss = staticmethod(uniform_loss_function)
|
|
43
|
+
dim = LossDim.LOSS_2D
|
|
44
|
+
usual_name = 'Uniform'
|
|
45
|
+
params = []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@LossFunctionFactory.register()
|
|
50
|
+
class ResolutionLoss(LossFunctionBase):
|
|
51
|
+
_loss = staticmethod(resolution_loss_function)
|
|
52
|
+
dim = LossDim.LOSS_2D
|
|
53
|
+
usual_name = 'Resolution'
|
|
54
|
+
params = [
|
|
55
|
+
{'title': 'Min:', 'name': 'min_distance', 'type': 'float', 'value': 0., 'min': 0., 'max': 1.},
|
|
56
|
+
{'title': 'Max:', 'name': 'max_distance', 'type': 'float', 'value': 1., 'min': 0., 'max': 1.},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@LossFunctionFactory.register()
|
|
61
|
+
class MinTriangleLoss(LossFunctionBase):
|
|
62
|
+
_loss = staticmethod(minimize_triangle_surface_loss_function)
|
|
63
|
+
dim = LossDim.LOSS_2D
|
|
64
|
+
usual_name = 'MinTriangle'
|
|
65
|
+
params = []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@LossFunctionFactory.register()
|
|
70
|
+
class ThresholdLoss(LossFunctionBase):
|
|
71
|
+
_loss = staticmethod(thresholded_loss_function)
|
|
72
|
+
dim = LossDim.LOSS_2D
|
|
73
|
+
usual_name = 'Threshold'
|
|
74
|
+
params = [
|
|
75
|
+
{'title': 'Lower:', 'name': 'lower_threshold', 'type': 'float', 'value': None,},
|
|
76
|
+
{'title': 'Upper:', 'name': 'upper_threshold', 'type': 'float', 'value': None,},
|
|
77
|
+
{'title': 'Priority factor:', 'name': 'priority_factor', 'type': 'float', 'value': 0.1,},
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@LossFunctionFactory.register()
|
|
82
|
+
class TriangleLoss(LossFunctionBase):
|
|
83
|
+
_loss = staticmethod(triangle_loss)
|
|
84
|
+
dim = LossDim.LOSS_2D
|
|
85
|
+
usual_name = 'Triangle'
|
|
86
|
+
params = []
|