From e6e29e60f9d1e22ed6ae40036807f872c6611016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Wed, 16 Oct 2024 14:55:00 +0200 Subject: [PATCH 1/7] implementing the model based datamixer where the default model is equation --- .../extensions/data_mixer.py | 90 ++++++---------- .../extensions/utils/model.py | 90 ++++++++++++++++ .../models/PIDModelTemplate.py | 100 ------------------ .../models/equation_model.py | 74 +++++++++++++ 4 files changed, 198 insertions(+), 156 deletions(-) create mode 100644 src/pymodaq_plugins_datamixer/extensions/utils/model.py delete mode 100644 src/pymodaq_plugins_datamixer/models/PIDModelTemplate.py create mode 100644 src/pymodaq_plugins_datamixer/models/equation_model.py diff --git a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py index 172aa95..3d210e4 100644 --- a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py +++ b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py @@ -4,6 +4,7 @@ from pymodaq_gui import utils as gutils from pymodaq_utils.config import Config, ConfigError from pymodaq_utils.logger import set_logger, get_module_name +from pymodaq_utils.utils import find_dict_in_list_from_key_val from pymodaq_data.data import DataToExport, DataWithAxes from pymodaq.utils.config import get_set_preset_path @@ -15,6 +16,7 @@ from pymodaq_plugins_datamixer.extensions.utils.parser import ( extract_data_names, split_formulae, replace_names_in_formula) from pymodaq.control_modules.utils import DAQTypesEnum +from pymodaq_plugins_datamixer.extensions.utils.model import get_models logger = set_logger(get_module_name(__file__)) @@ -28,19 +30,31 @@ class DataMixer(CustomExt): settings_name = 'DataMixerSettings' - params = [{'title': 'Edit Formula:', 'name': 'edit_formula', 'type': 'text', 'value': ''}] + models = get_models() + params = [ + {'title': 'Models', 'name': 'models', 'type': 'group', 'expanded': True, 'visible': True, + 'children': [ + {'title': 'Models class:', 'name': 'model_class', 'type': 'list', + 'limits': [d['name'] for d in models]}, + {'title': 'Model params:', 'name': 'model_params', 'type': 'group', 'children': []}, + + ]}] + dte_computed_signal = QtCore.Signal(DataToExport) def __init__(self, parent: gutils.DockArea, dashboard): super().__init__(parent, dashboard) - self.data0D_list_widget = ItemSelect() - self.data1D_list_widget = ItemSelect() - self.data2D_list_widget = ItemSelect() - self.dataND_list_widget = ItemSelect() - self.setup_ui() + def get_set_model_params(self, model_name): + self.settings.child('models', 'model_params').clearChildren() + if len(self.models) > 0: + model_class = find_dict_in_list_from_key_val(self.models, 'name', model_name)['class'] + params = getattr(model_class, 'params') + self.settings.child('models', 'model_params').addChildren(params) + + def setup_docks(self): """Mandatory method to be subclassed to setup the docks layout @@ -92,6 +106,9 @@ def setup_docks(self): self.docks['computed'].addWidget(self.area_computed) self.dte_computed_viewer = ViewerDispatcher(self.area_computed) + if len(self.models) != 0: + self.get_set_model_params(self.models[0]['name']) + def setup_actions(self): """Method where to create actions to be subclassed. Mandatory @@ -164,56 +181,16 @@ def connect_detectors(self, connect=True): def plot_formulae_results(self, dte): self.dte_computed_viewer.show_data(dte) - def process_data(self, dte: DataToExport): - if self.is_action_checked('compute'): - formulae = split_formulae(self.get_formulae()) - dte_processed = DataToExport('Computed') - for ind, formula in enumerate(formulae): - try: - dwa = self.compute_formula(formula, dte, - name=f'Formula_{ind:03.0f}') - dte_processed.append(dwa) - except Exception as e: - pass - self.dte_computed_signal.emit(dte_processed) - - def compute_formula(self, formula: str, dte: DataToExport, - name: str) -> DataWithAxes: - """ Compute the operations in formula using data stored in dte - - Parameters - ---------- - formula: str - The mathematical formula using numpy and data fullnames within curly brackets - dte: DataToExport - name: str - The name to give to the produced DataWithAxes - - Returns - ------- - DataWithAxes: the results of the formula computation - """ - formula_to_eval, names = replace_names_in_formula(formula) - dwa = eval(formula_to_eval) - dwa.name = name - return dwa - - def get_formulae(self) -> str: - """ Read the content of the formula QTextEdit widget""" - return self.settings['edit_formula'] - - def show_data_list(self): - dte = self.modules_manager.get_det_data_list() - - data_list0D = dte.get_full_names('data0D') - data_list1D = dte.get_full_names('data1D') - data_list2D = dte.get_full_names('data2D') - data_listND = dte.get_full_names('dataND') + def ini_model(self): + try: + if self.model_class is None: + self.set_model() - self.data0D_list_widget.set_value(dict(all_items=data_list0D, selected=[])) - self.data1D_list_widget.set_value(dict(all_items=data_list1D, selected=[])) - self.data2D_list_widget.set_value(dict(all_items=data_list2D, selected=[])) - self.dataND_list_widget.set_value(dict(all_items=data_listND, selected=[])) + def set_model(self): + model_name = self.settings.child('models', 'model_class').value() + self.model_class = find_dict_in_list_from_key_val( + self.models, 'name', model_name)['class'](self) + self.model_class.ini_model_base() def setup_menu(self): """Non mandatory method to be subclassed in order to create a menubar @@ -250,7 +227,8 @@ def value_changed(self, param): ---------- param: (Parameter) the parameter whose value just changed """ - pass + if param.name() == 'model_class': + self.get_set_model_params(param.value()) def quit(self): self.mainwindow.close() diff --git a/src/pymodaq_plugins_datamixer/extensions/utils/model.py b/src/pymodaq_plugins_datamixer/extensions/utils/model.py new file mode 100644 index 0000000..a451597 --- /dev/null +++ b/src/pymodaq_plugins_datamixer/extensions/utils/model.py @@ -0,0 +1,90 @@ +from typing import List, TYPE_CHECKING +import importlib +import inspect +import pkgutil +import warnings +from pathlib import Path +from typing import Union, List + +from pymodaq_utils.utils import find_dict_in_list_from_key_val, get_entrypoints +from pymodaq_utils.logger import set_logger, get_module_name + +from pymodaq_data.data import DataToExport + +from pymodaq_gui.managers.parameter_manager import ParameterManager + +logger = set_logger(get_module_name(__file__)) + +if TYPE_CHECKING: + from pymodaq.utils.managers.modules_manager import ModulesManager + + +class DataMixerModel(ParameterManager): + + detectors_name: List[str] = [] + params = [] + + def __init__(self, modules_manager: 'ModulesManager'): + self.modules_manager = modules_manager + self.check_modules(modules_manager) + + def check_modules(self, modules_manager: 'ModulesManager'): + for det in self.detectors_name: + if det not in modules_manager.detectors_name: + logger.warning(f'The detector {det} defined in the DataMixer model is' + f' not present in the Dashboard') + + def ini_model(self): + pass + + def process_dte(self, measurements: DataToExport) -> DataToExport: + """ + Convert the measurements in the units to be fed to the PID (same dimensionality as the setpoint) + Parameters + ---------- + measurements: DataToExport + DataToExport object from which the model extract a value of the same units as the setpoint + + Returns + ------- + DataToExport: the converted input as 0D DataCalculated stored in a DataToExport + """ + raise NotImplementedError + + + +def get_models(model_name=None): + """ + Get DataMixer Models + + Returns + ------- + list: list of dict containting the name and python module of the found models + """ + models_import = [] + discovered_models = list(get_entrypoints(group='pymodaq.models')) + if len(discovered_models) > 0: + for pkg in discovered_models: + try: + module = importlib.import_module(pkg.value) + module_name = pkg.value + + for mod in pkgutil.iter_modules([str(Path(module.__file__).parent.joinpath('models'))]): + try: + model_module = importlib.import_module(f'{module_name}.models.{mod.name}', module) + classes = inspect.getmembers(model_module, inspect.isclass) + for name, klass in classes: + if klass.__base__ is DataMixerModel: + models_import.append({'name': mod.name, 'module': model_module, 'class': klass}) + break + + except Exception as e: # pragma: no cover + logger.warning(str(e)) + + except Exception as e: # pragma: no cover + logger.warning(f'Impossible to import the {pkg.value} extension: {str(e)}') + + if model_name is None: + return models_import + else: + return find_dict_in_list_from_key_val(models_import, 'name', model_name) diff --git a/src/pymodaq_plugins_datamixer/models/PIDModelTemplate.py b/src/pymodaq_plugins_datamixer/models/PIDModelTemplate.py deleted file mode 100644 index 2b2dce8..0000000 --- a/src/pymodaq_plugins_datamixer/models/PIDModelTemplate.py +++ /dev/null @@ -1,100 +0,0 @@ -import numpy as np - -from pymodaq.extensions.pid.utils import PIDModelGeneric, DataToActuatorPID, main -from pymodaq_data.data import DataToExport, DataCalculated - -from pymodaq.utils.data import DataActuator - -from typing import List - - -def some_function_to_convert_the_pid_outputs(outputs: List[float], dt: float, stab=True): - """ Should be replaced here or in the model class to process the outputs """ - return outputs - - -def some_function_to_convert_the_data(measurements: DataToExport): - """ Should be replaced here or in the model class to process the measurement """ - a = 0 - b = 1 - return [a, b] - - -class PIDModelTemplate(PIDModelGeneric): - limits = dict(max=dict(state=False, value=100), - min=dict(state=False, value=-100),) - konstants = dict(kp=0.1, ki=0.000, kd=0.0000) - - Nsetpoints = 2 # number of setpoints - setpoint_ini = [128, 128] # number and values of initial setpoints - setpoints_names = ['Xaxis', 'Yaxis'] # number and names of setpoints - - actuators_name = ["Xpiezo", "Ypiezo"] # names of actuator's control modules involved in the PID - detectors_name = ['Camera'] # names of detector's control modules involved in the PID - - params = [] # list of dict to initialize specific Parameters - - def __init__(self, pid_controller): - super().__init__(pid_controller) - - def update_settings(self, param): - """ - Get a parameter instance whose value has been modified by a user on the UI - Parameters - ---------- - param: (Parameter) instance of Parameter object - """ - if param.name() == '': - pass - - def ini_model(self): - super().ini_model() - - # add here other specifics initialization if needed - - def convert_input(self, measurements: DataToExport): - """ - Convert the measurements in the units to be fed to the PID (same dimensionality as the setpoint) - Parameters - ---------- - measurements: DataToExport - Data from the declared detectors from which the model extract a value of the same units as the setpoint - - Returns - ------- - InputFromDetector: the converted input in the setpoints units - - """ - - x, y = some_function_to_convert_the_data(measurements) - return DataToExport('pid inputs', - data=[DataCalculated('pid calculated', - data=[np.array([x]), - np.array([y])])]) - - def convert_output(self, outputs: List[float], dt: float, stab=True) -> DataToActuatorPID: - """ - Convert the output of the PID in units to be fed into the actuator - Parameters - ---------- - outputs: List of float - output value from the PID from which the model extract a value of the same units as the actuator - dt: float - Ellapsed time since the last call to this function - stab: bool - - Returns - ------- - OutputToActuator: the converted output - - """ - outputs = some_function_to_convert_the_pid_outputs(outputs, dt, stab) - return DataToActuatorPID('pid output', mode='rel', - data=[DataActuator(self.actuators_name[ind], data=outputs[ind]) - for ind in range(len(outputs))]) - - -if __name__ == '__main__': - main("BeamSteeringMockNoModel.xml") # some preset configured with the right actuators and detectors - - diff --git a/src/pymodaq_plugins_datamixer/models/equation_model.py b/src/pymodaq_plugins_datamixer/models/equation_model.py new file mode 100644 index 0000000..6a0e473 --- /dev/null +++ b/src/pymodaq_plugins_datamixer/models/equation_model.py @@ -0,0 +1,74 @@ +from pymodaq_plugins_datamixer.extensions.utils.model import DataMixerModel + +from pymodaq_data.data import DataToExport, DataWithAxes + +from pymodaq_plugins_datamixer.extensions.utils.parser import ( + extract_data_names, split_formulae, replace_names_in_formula) + + +class DataMixerModelEquation(DataMixerModel): + param = [ + {'title': 'Edit Formula:', 'name': 'edit_formula', 'type': 'text', 'value': ''}, + {'title': 'Data0D:', 'name': 'data0D', 'type': 'item_select', + 'value': dict(all_items=[], selected=[])}, + {'title': 'Data1D:', 'name': 'data1D', 'type': 'item_select', + 'value': dict(all_items=[], selected=[])}, + {'title': 'Data2D:', 'name': 'data2D', 'type': 'item_select', + 'value': dict(all_items=[], selected=[])}, + + + ] + + pass + + + def get_formulae(self) -> str: + """ Read the content of the formula QTextEdit widget""" + return self.settings['edit_formula'] + + def show_data_list(self): + dte = self.modules_manager.get_det_data_list() + + data_list0D = dte.get_full_names('data0D') + data_list1D = dte.get_full_names('data1D') + data_list2D = dte.get_full_names('data2D') + data_listND = dte.get_full_names('dataND') + + self.data0D_list_widget.set_value(dict(all_items=data_list0D, selected=[])) + self.data1D_list_widget.set_value(dict(all_items=data_list1D, selected=[])) + self.data2D_list_widget.set_value(dict(all_items=data_list2D, selected=[])) + self.dataND_list_widget.set_value(dict(all_items=data_listND, selected=[])) + + def process_dte(self, dte: DataToExport): + formulae = split_formulae(self.get_formulae()) + dte_processed = DataToExport('Computed') + for ind, formula in enumerate(formulae): + try: + dwa = self.compute_formula(formula, dte, + name=f'Formula_{ind:03.0f}') + dte_processed.append(dwa) + except Exception as e: + pass + return dte_processed + + def compute_formula(self, formula: str, dte: DataToExport, + name: str) -> DataWithAxes: + """ Compute the operations in formula using data stored in dte + + Parameters + ---------- + formula: str + The mathematical formula using numpy and data fullnames within curly brackets + dte: DataToExport + name: str + The name to give to the produced DataWithAxes + + Returns + ------- + DataWithAxes: the results of the formula computation + """ + formula_to_eval, names = replace_names_in_formula(formula) + dwa = eval(formula_to_eval) + dwa.name = name + return dwa + From 2523c690e65f7a79fff957c00908e90a864f5b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Thu, 17 Oct 2024 17:16:10 +0200 Subject: [PATCH 2/7] adding models in config --- plugin_info.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin_info.toml b/plugin_info.toml index 197e88d..118ff2c 100644 --- a/plugin_info.toml +++ b/plugin_info.toml @@ -13,12 +13,12 @@ license = 'MIT' [plugin-install] #packages required for your plugin: -packages-required = ['pymodaq>=5.0.1', 'pymodaq_data>=0.0.1'] +packages-required = ['pymodaq>=5.0.0', 'pymodaq_data>=0.0.1'] [features] # defines the plugin features contained into this plugin instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) extensions = true # true if plugins contains dashboard extensions -models = false # true if plugins contains pid models or other models (optimisation...) +models = true # true if plugins contains pid models or other models (optimisation...) h5exporters = false # true if plugin contains custom h5 file exporters scanners = false # true if plugin contains custom scan layout (daq_scan extensions) From aae88163d6faeb0c7a68774c8f200e5e9fb779c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Thu, 17 Oct 2024 17:16:42 +0200 Subject: [PATCH 3/7] defining model base class and finishing extension --- .../extensions/data_mixer.py | 81 ++++++++----------- .../extensions/utils/model.py | 26 +++--- 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py index 3d210e4..018079e 100644 --- a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py +++ b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py @@ -1,6 +1,8 @@ from qtpy import QtWidgets, QtCore import numpy as np +from typing import Optional + from pymodaq_gui import utils as gutils from pymodaq_utils.config import Config, ConfigError from pymodaq_utils.logger import set_logger, get_module_name @@ -9,14 +11,14 @@ from pymodaq.utils.config import get_set_preset_path from pymodaq.extensions.utils import CustomExt -from pymodaq_gui.parameter.pymodaq_ptypes.itemselect import ItemSelect + from pymodaq_gui.plotting.data_viewers.viewer import ViewerDispatcher +from pymodaq_gui.utils.widgets.qled import QLED +from pymodaq_gui.parameter import utils as putils from pymodaq_plugins_datamixer.utils import Config as PluginConfig -from pymodaq_plugins_datamixer.extensions.utils.parser import ( - extract_data_names, split_formulae, replace_names_in_formula) from pymodaq.control_modules.utils import DAQTypesEnum -from pymodaq_plugins_datamixer.extensions.utils.model import get_models +from pymodaq_plugins_datamixer.extensions.utils.model import get_models, DataMixerModel logger = set_logger(get_module_name(__file__)) @@ -36,6 +38,7 @@ class DataMixer(CustomExt): 'children': [ {'title': 'Models class:', 'name': 'model_class', 'type': 'list', 'limits': [d['name'] for d in models]}, + {'title': 'Ini Model', 'name': 'ini_model', 'type': 'action', }, {'title': 'Model params:', 'name': 'model_params', 'type': 'group', 'children': []}, ]}] @@ -45,8 +48,13 @@ class DataMixer(CustomExt): def __init__(self, parent: gutils.DockArea, dashboard): super().__init__(parent, dashboard) + self.model_class: Optional[DataMixerModel] = None + self.setup_ui() + self.settings.child('models', 'ini_model').sigActivated.connect( + self.get_action('ini_model').trigger) + def get_set_model_params(self, model_name): self.settings.child('models', 'model_params').clearChildren() if len(self.models) > 0: @@ -58,13 +66,6 @@ def get_set_model_params(self, model_name): def setup_docks(self): """Mandatory method to be subclassed to setup the docks layout - Examples - -------- - >>>self.docks['ADock'] = gutils.Dock('ADock name') - >>>self.dockarea.addDock(self.docks['ADock']) - >>>self.docks['AnotherDock'] = gutils.Dock('AnotherDock name') - >>>self.dockarea.addDock(self.docks['AnotherDock'''], 'bottom', self.docks['ADock']) - See Also -------- pyqtgraph.dockarea.Dock @@ -85,20 +86,6 @@ def setup_docks(self): splitter.addWidget(self.settings_tree) - self.docks['data'] = gutils.Dock('Data List') - self.dockarea.addDock(self.docks['data'], 'right') - widget_data = QtWidgets.QWidget() - widget_data.setLayout(QtWidgets.QVBoxLayout()) - self.docks['data'].addWidget(widget_data) - widget_data.layout().addWidget(QtWidgets.QLabel('Data0D:')) - widget_data.layout().addWidget(self.data0D_list_widget) - widget_data.layout().addWidget(QtWidgets.QLabel('Data1D:')) - widget_data.layout().addWidget(self.data1D_list_widget) - widget_data.layout().addWidget(QtWidgets.QLabel('Data2D:')) - widget_data.layout().addWidget(self.data2D_list_widget) - widget_data.layout().addWidget(QtWidgets.QLabel('DataND:')) - widget_data.layout().addWidget(self.dataND_list_widget) - self.docks['computed'] = gutils.Dock('Computed data') self.dockarea.addDock(self.docks['computed'], 'right') @@ -112,24 +99,13 @@ def setup_docks(self): def setup_actions(self): """Method where to create actions to be subclassed. Mandatory - Examples - -------- - >>> self.add_action('quit', 'Quit', 'close2', "Quit program") - >>> self.add_action('grab', 'Grab', 'camera', "Grab from camera", checkable=True) - >>> self.add_action('load', 'Load', 'Open', "Load target file (.h5, .png, .jpg) or data from camera" - , checkable=False) - >>> self.add_action('save', 'Save', 'SaveAs', "Save current data", checkable=False) - See Also -------- ActionManager.add_action """ self.add_action('quit', 'Quit', 'close2', "Quit program") - self.add_action('get_data', 'Get Data List', 'properties', - "Get the list of data from selected detectors") - self.add_action('compute', 'Compute Formulae', 'algo', - 'Compute the Formula when new data is available', - checkable=True) + self.add_action('ini_model', 'Init Model', 'ini') + self.add_widget('model_led', QLED, toolbar=self.toolbar) self.add_action('snap', 'Snap Detectors', 'snap', 'Snap all selected detectors') self.add_action('create_computed_detectors', 'Create Computed Detectors', 'Add_Step', @@ -138,23 +114,24 @@ def setup_actions(self): def connect_things(self): """Connect actions and/or other widgets signal to methods""" self.connect_action('quit', self.quit) - self.connect_action('get_data', self.show_data_list) + self.connect_action('ini_model', self.ini_model) self.modules_manager.det_done_signal.connect(self.process_data) - self.dte_computed_signal.connect(self.plot_formulae_results) + self.dte_computed_signal.connect(self.plot_computed_results) self.connect_action('snap', self.snap) self.modules_manager.detectors_changed.connect(self.update_connect_detectors) self.connect_action('create_computed_detectors', self.create_computed_detectors) + def process_data(self, dte: DataToExport): + if self.model_class is not None: + dte_computed = self.model_class.process_dte(dte) + self.dte_computed_signal.emit(dte_computed) + def snap(self): self.modules_manager.grab_data() def create_computed_detectors(self): # Now that we have the module manager, load PID if it is checked in managers try: - detector_modules = [] - self.dashboard.add_det('DataMixer', None, [], [], detector_modules, - plug_type=DAQTypesEnum.DAQ0D.name, - plug_subtype='DataMixer') self.dashboard.add_det_from_extension('DataMixer', 'DAQ0D', 'DataMixer', self) self.set_action_enabled('create_computed_detectors', False) except Exception as e: @@ -178,13 +155,18 @@ def connect_detectors(self, connect=True): """ self.modules_manager.connect_detectors(connect=connect) - def plot_formulae_results(self, dte): + def plot_computed_results(self, dte): self.dte_computed_viewer.show_data(dte) def ini_model(self): - try: - if self.model_class is None: - self.set_model() + if self.model_class is None: + self.set_model() + + self.get_action('model_led').set_as_true() + self.set_action_enabled('ini_model', False) + self.settings.child('models', 'ini_model').setValue(True) + + self.update_connect_detectors() def set_model(self): model_name = self.settings.child('models', 'model_class').value() @@ -229,6 +211,9 @@ def value_changed(self, param): """ if param.name() == 'model_class': self.get_set_model_params(param.value()) + elif param.name() in putils.iter_children(self.settings.child('models', 'model_params'), []): + if self.model_class is not None: + self.model_class.update_settings(param) def quit(self): self.mainwindow.close() diff --git a/src/pymodaq_plugins_datamixer/extensions/utils/model.py b/src/pymodaq_plugins_datamixer/extensions/utils/model.py index a451597..0e4a888 100644 --- a/src/pymodaq_plugins_datamixer/extensions/utils/model.py +++ b/src/pymodaq_plugins_datamixer/extensions/utils/model.py @@ -6,37 +6,42 @@ from pathlib import Path from typing import Union, List +import numpy as np # to be imported within models + from pymodaq_utils.utils import find_dict_in_list_from_key_val, get_entrypoints from pymodaq_utils.logger import set_logger, get_module_name from pymodaq_data.data import DataToExport -from pymodaq_gui.managers.parameter_manager import ParameterManager +from pymodaq_gui.managers.parameter_manager import ParameterManager, Parameter logger = set_logger(get_module_name(__file__)) if TYPE_CHECKING: + from pymodaq_plugins_datamixer.extensions.data_mixer import DataMixer from pymodaq.utils.managers.modules_manager import ModulesManager -class DataMixerModel(ParameterManager): +class DataMixerModel: detectors_name: List[str] = [] params = [] - def __init__(self, modules_manager: 'ModulesManager'): - self.modules_manager = modules_manager - self.check_modules(modules_manager) + def __init__(self, data_mixer: 'DataMixer'): + self.data_mixer = data_mixer + self.modules_manager: ModulesManager = data_mixer.dashboard.modules_manager + self.settings: Parameter = self.data_mixer.settings.child('models', 'model_params') - def check_modules(self, modules_manager: 'ModulesManager'): - for det in self.detectors_name: - if det not in modules_manager.detectors_name: - logger.warning(f'The detector {det} defined in the DataMixer model is' - f' not present in the Dashboard') + def ini_model_base(self): + """ Method to add things that should be executed before instantiating the model""" + self.ini_model() def ini_model(self): pass + def update_settings(self, param: Parameter): + pass + def process_dte(self, measurements: DataToExport) -> DataToExport: """ Convert the measurements in the units to be fed to the PID (same dimensionality as the setpoint) @@ -52,7 +57,6 @@ def process_dte(self, measurements: DataToExport) -> DataToExport: raise NotImplementedError - def get_models(model_name=None): """ Get DataMixer Models From f14a024448a84c47be84609de573dfe1284417ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Thu, 17 Oct 2024 17:17:03 +0200 Subject: [PATCH 4/7] from the first implementation, created this model to compute formulae --- .../models/equation_model.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/pymodaq_plugins_datamixer/models/equation_model.py b/src/pymodaq_plugins_datamixer/models/equation_model.py index 6a0e473..45a16d8 100644 --- a/src/pymodaq_plugins_datamixer/models/equation_model.py +++ b/src/pymodaq_plugins_datamixer/models/equation_model.py @@ -1,26 +1,33 @@ -from pymodaq_plugins_datamixer.extensions.utils.model import DataMixerModel +from pymodaq_plugins_datamixer.extensions.utils.model import DataMixerModel, np # np will be used in method eval of the formula from pymodaq_data.data import DataToExport, DataWithAxes +from pymodaq_gui.parameter import Parameter from pymodaq_plugins_datamixer.extensions.utils.parser import ( extract_data_names, split_formulae, replace_names_in_formula) class DataMixerModelEquation(DataMixerModel): - param = [ + params = [ + {'title': 'Get Data:', 'name': 'get_data', 'type': 'bool_push', 'value': False, + 'label': 'Get Data'}, {'title': 'Edit Formula:', 'name': 'edit_formula', 'type': 'text', 'value': ''}, - {'title': 'Data0D:', 'name': 'data0D', 'type': 'item_select', + {'title': 'Data0D:', 'name': 'data0D', 'type': 'itemselect', 'value': dict(all_items=[], selected=[])}, - {'title': 'Data1D:', 'name': 'data1D', 'type': 'item_select', + {'title': 'Data1D:', 'name': 'data1D', 'type': 'itemselect', 'value': dict(all_items=[], selected=[])}, - {'title': 'Data2D:', 'name': 'data2D', 'type': 'item_select', + {'title': 'Data2D:', 'name': 'data2D', 'type': 'itemselect', + 'value': dict(all_items=[], selected=[])}, + {'title': 'DataND:', 'name': 'dataND', 'type': 'itemselect', 'value': dict(all_items=[], selected=[])}, - - ] - pass + def ini_model(self): + self.show_data_list() + def update_settings(self, param: Parameter): + if param.name() == 'get_data': + self.show_data_list() def get_formulae(self) -> str: """ Read the content of the formula QTextEdit widget""" @@ -34,10 +41,10 @@ def show_data_list(self): data_list2D = dte.get_full_names('data2D') data_listND = dte.get_full_names('dataND') - self.data0D_list_widget.set_value(dict(all_items=data_list0D, selected=[])) - self.data1D_list_widget.set_value(dict(all_items=data_list1D, selected=[])) - self.data2D_list_widget.set_value(dict(all_items=data_list2D, selected=[])) - self.dataND_list_widget.set_value(dict(all_items=data_listND, selected=[])) + self.settings.child('data0D').setValue(dict(all_items=data_list0D, selected=[])) + self.settings.child('data1D').setValue(dict(all_items=data_list1D, selected=[])) + self.settings.child('data2D').setValue(dict(all_items=data_list2D, selected=[])) + self.settings.child('dataND').setValue(dict(all_items=data_listND, selected=[])) def process_dte(self, dte: DataToExport): formulae = split_formulae(self.get_formulae()) From dbe7e00fc6a3092f53d3ab615b9be2a8ec3d0b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Thu, 17 Oct 2024 17:17:27 +0200 Subject: [PATCH 5/7] another model example to show how to fit some data --- .../models/fit_model.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/pymodaq_plugins_datamixer/models/fit_model.py diff --git a/src/pymodaq_plugins_datamixer/models/fit_model.py b/src/pymodaq_plugins_datamixer/models/fit_model.py new file mode 100644 index 0000000..8088957 --- /dev/null +++ b/src/pymodaq_plugins_datamixer/models/fit_model.py @@ -0,0 +1,48 @@ +import numpy as np + +from pymodaq_plugins_datamixer.extensions.utils.model import DataMixerModel, np # np will be used in method eval of the formula + +from pymodaq_utils.math_utils import gauss1D, my_moment + +from pymodaq_data.data import DataToExport, DataWithAxes +from pymodaq_gui.parameter import Parameter + +from pymodaq_plugins_datamixer.extensions.utils.parser import ( + extract_data_names, split_formulae, replace_names_in_formula) + + +def gaussian_fit(x, amp, x0, dx, offset): + return amp * gauss1D(x, x0, dx) + offset + + +class DataMixerModelFit(DataMixerModel): + params = [ + + ] + + def ini_model(self): + pass + + def update_settings(self, param: Parameter): + pass + + def process_dte(self, dte: DataToExport): + dte_processed = DataToExport('computed') + dwa = dte.get_data_from_full_name('Spectrum - ROI_00/Hlineout_ROI_00').deepcopy() + + dte_processed.append(dwa) + dte_processed.append(dwa.fit(gaussian_fit, self.get_guess(dwa))) + + return dte_processed + + @staticmethod + def get_guess(dwa): + offset = np.min(dwa).value() + moments = my_moment(dwa.axes[0].get_data(), dwa.data[0]) + amp = (np.max(dwa) - np.min(dwa)).value() + x0 = float(moments[0]) + dx = float(moments[1]) + + return amp, x0, dx, offset + + From 72712d1038bc67754453a420d8dd65b142652a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Thu, 5 Dec 2024 10:53:38 +0100 Subject: [PATCH 6/7] Delete custom_app_template.py --- .../app/custom_app_template.py | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 src/pymodaq_plugins_datamixer/app/custom_app_template.py diff --git a/src/pymodaq_plugins_datamixer/app/custom_app_template.py b/src/pymodaq_plugins_datamixer/app/custom_app_template.py deleted file mode 100644 index ec46ad8..0000000 --- a/src/pymodaq_plugins_datamixer/app/custom_app_template.py +++ /dev/null @@ -1,125 +0,0 @@ -from qtpy import QtWidgets - -from pymodaq_gui import utils as gutils -from pymodaq_utils.config import Config -from pymodaq_utils.logger import set_logger, get_module_name - -# todo: replace here *pymodaq_plugins_template* by your plugin package name -from pymodaq_plugins_template.utils import Config as PluginConfig - -logger = set_logger(get_module_name(__file__)) - -main_config = Config() -plugin_config = PluginConfig() - - -# todo: modify the name of this class to reflect its application and change the name in the main -# method at the end of the script -class CustomAppTemplate(gutils.CustomApp): - - # todo: if you wish to create custom Parameter and corresponding widgets. These will be - # automatically added as children of self.settings. Morevover, the self.settings_tree will - # render the widgets in a Qtree. If you wish to see it in your app, add is into a Dock - params = [] - - def __init__(self, parent: gutils.DockArea): - super().__init__(parent) - - self.setup_ui() - - def setup_docks(self): - """Mandatory method to be subclassed to setup the docks layout - - Examples - -------- - >>>self.docks['ADock'] = gutils.Dock('ADock name') - >>>self.dockarea.addDock(self.docks['ADock']) - >>>self.docks['AnotherDock'] = gutils.Dock('AnotherDock name') - >>>self.dockarea.addDock(self.docks['AnotherDock'''], 'bottom', self.docks['ADock']) - - See Also - -------- - pyqtgraph.dockarea.Dock - """ - # todo: create docks and add them here to hold your widgets - # reminder, the attribute self.settings_tree will render the widgets in a Qtree. - # If you wish to see it in your app, add is into a Dock - raise NotImplementedError - - def setup_actions(self): - """Method where to create actions to be subclassed. Mandatory - - Examples - -------- - >>> self.add_action('quit', 'Quit', 'close2', "Quit program") - >>> self.add_action('grab', 'Grab', 'camera', "Grab from camera", checkable=True) - >>> self.add_action('load', 'Load', 'Open', "Load target file (.h5, .png, .jpg) or data from camera" - , checkable=False) - >>> self.add_action('save', 'Save', 'SaveAs', "Save current data", checkable=False) - - See Also - -------- - ActionManager.add_action - """ - raise NotImplementedError(f'You have to define actions here') - - def connect_things(self): - """Connect actions and/or other widgets signal to methods""" - raise NotImplementedError - - def setup_menu(self): - """Non mandatory method to be subclassed in order to create a menubar - - create menu for actions contained into the self._actions, for instance: - - Examples - -------- - >>>file_menu = self.mainwindow.menuBar().addMenu('File') - >>>self.affect_to('load', file_menu) - >>>self.affect_to('save', file_menu) - - >>>file_menu.addSeparator() - >>>self.affect_to('quit', file_menu) - - See Also - -------- - pymodaq.utils.managers.action_manager.ActionManager - """ - # todo create and populate menu using actions defined above in self.setup_actions - pass - - def value_changed(self, param): - """ Actions to perform when one of the param's value in self.settings is changed from the - user interface - - For instance: - if param.name() == 'do_something': - if param.value(): - print('Do something') - self.settings.child('main_settings', 'something_done').setValue(False) - - Parameters - ---------- - param: (Parameter) the parameter whose value just changed - """ - pass - - -def main(): - from pymodaq.utils.gui_utils.utils import mkQApp - app = mkQApp('CustomApp') - - mainwindow = QtWidgets.QMainWindow() - dockarea = gutils.DockArea() - mainwindow.setCentralWidget(dockarea) - - # todo: change the name here to be the same as your app class - prog = CustomAppTemplate(dockarea) - - mainwindow.show() - - app.exec() - - -if __name__ == '__main__': - main() From f7169d6a318ebbd1e1fbea37841d8dac9b9418db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Weber?= Date: Mon, 14 Apr 2025 11:04:16 +0200 Subject: [PATCH 7/7] resolve conflicts --- plugin_info.toml | 24 ------------------- .../extensions/data_mixer.py | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 plugin_info.toml diff --git a/plugin_info.toml b/plugin_info.toml deleted file mode 100644 index 118ff2c..0000000 --- a/plugin_info.toml +++ /dev/null @@ -1,24 +0,0 @@ -## To modify by developer(s) of the plugin - -[plugin-info] -SHORT_PLUGIN_NAME = 'datamixer' #to be modified, for instance daqmx then rename the module name: -# (pymodaq_plugins_template become pymodaq_plugins_daqmx for instance) - -package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_datamixer' #to modify -description = 'Implements an extension to manipulate data from several DAQ_Viewers' - -author = 'Sebastien J. Weber' -author-email = 'sebastien.weber@cnrs.fr' -license = 'MIT' - -[plugin-install] -#packages required for your plugin: -packages-required = ['pymodaq>=5.0.0', 'pymodaq_data>=0.0.1'] - -[features] # defines the plugin features contained into this plugin -instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) -extensions = true # true if plugins contains dashboard extensions -models = true # true if plugins contains pid models or other models (optimisation...) -h5exporters = false # true if plugin contains custom h5 file exporters -scanners = false # true if plugin contains custom scan layout (daq_scan extensions) - diff --git a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py index 018079e..f66dce3 100644 --- a/src/pymodaq_plugins_datamixer/extensions/data_mixer.py +++ b/src/pymodaq_plugins_datamixer/extensions/data_mixer.py @@ -220,7 +220,7 @@ def quit(self): def main(): - from pymodaq.utils.gui_utils.utils import mkQApp + from pymodaq_gui.utils.utils import mkQApp from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset app = mkQApp('DataMixer')