From 1e6834baa370cc3f9151e08a23631696a4889625 Mon Sep 17 00:00:00 2001 From: jbrazard Date: Tue, 20 Jan 2026 12:03:42 +0100 Subject: [PATCH 1/9] starting a new instrument plugin for my monochromator copied from template --- setup.py | 4 - .../daq_move_Monochromator.py | 199 ++++++++++++++++++ .../daq_move_plugins/daq_move_Power.py | 144 ------------- 3 files changed, 199 insertions(+), 148 deletions(-) delete mode 100644 setup.py create mode 100644 src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py delete mode 100644 src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Power.py diff --git a/setup.py b/setup.py deleted file mode 100644 index c85f63a..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from pymodaq.resources.setup_plugin import setup -from pathlib import Path - -setup(Path(__file__).parent) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py new file mode 100644 index 0000000..25d721b --- /dev/null +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -0,0 +1,199 @@ + +from typing import Union, List, Dict +from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, + main, DataActuatorType, DataActuator) + +from pymodaq_utils.utils import ThreadCommand # object used to send info back to the main thread +from pymodaq_gui.parameter import Parameter + +# TODO: +# Replace the following fake import with the import of the real Python wrapper of your instrument. Here we suppose that +# the wrapper is in the hardware directory, but it could come from an external librairy like pylablib or pymeasure. +from pymodaq_plugins_template.hardware.python_wrapper_file_of_your_instrument import PythonWrapperObjectOfYourInstrument + +# TODO: +# (1) change the name of the following class to DAQ_Move_TheNameOfYourChoice +# (2) change the name of this file to daq_move_TheNameOfYourChoice ("TheNameOfYourChoice" should be the SAME +# for the class name and the file name.) +# (3) this file should then be put into the right folder, namely IN THE FOLDER OF THE PLUGIN YOU ARE DEVELOPING: +# pymodaq_plugins_my_plugin/daq_move_plugins + + +class DAQ_Move_Monochromator(DAQ_Move_base): + """ Instrument plugin class for an actuator. + + This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Move module through inheritance via + DAQ_Move_base. It makes a bridge between the DAQ_Move module and the Python wrapper of a particular instrument. + + TODO Complete the docstring of your plugin with: + * The set of controllers and actuators that should be compatible with this instrument plugin. + * With which instrument and controller it has been tested. + * The version of PyMoDAQ during the test. + * The version of the operating system. + * Installation instructions: what manufacturer’s drivers should be installed to make it run? + + Attributes: + ----------- + controller: object + The particular object that allow the communication with the hardware, in general a python wrapper around the + hardware library. + + # TODO add your particular attributes here if any + + """ + is_multiaxes = False # TODO for your plugin set to True if this plugin is controlled for a multiaxis controller + _axis_names: Union[List[str], Dict[str, int]] = ['Axis1', 'Axis2'] # TODO for your plugin: complete the list + _controller_units: Union[str, List[str]] = 'mm' # TODO for your plugin: put the correct unit here, it could be + # TODO a single str (the same one is applied to all axes) or a list of str (as much as the number of axes) + _epsilon: Union[float, List[float]] = 0.1 # TODO replace this by a value that is correct depending on your controller + # TODO it could be a single float of a list of float (as much as the number of axes) + data_actuator_type = DataActuatorType.DataActuator # wether you use the new data style for actuator otherwise set this + # as DataActuatorType.float (or entirely remove the line) + + params = [ # TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached + # the target value. It is the developer responsibility to put here a meaningful value + + def ini_attributes(self): + # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy + # autocompletion + self.controller: PythonWrapperObjectOfYourInstrument = None + + #TODO declare here attributes you want/need to init with a default value + pass + + def get_actuator_value(self): + """Get the current value from the hardware with scaling conversion. + + Returns + ------- + float: The position obtained after scaling conversion. + """ + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + pos = DataActuator(data=self.controller.your_method_to_get_the_actuator_value(), # when writing your own plugin replace this line + units=self.axis_unit) + pos = self.get_position_with_scaling(pos) + return pos + + def user_condition_to_reach_target(self) -> bool: + """ Implement a condition for exiting the polling mechanism and specifying that the + target value has been reached + + Returns + ------- + bool: if True, PyMoDAQ considers the target value has been reached + """ + # TODO either delete this method if the usual polling is fine with you, but if need you can + # add here some other condition to be fullfilled either a completely new one or + # using or/and operations between the epsilon_bool and some other custom booleans + # for a usage example see DAQ_Move_brushlessMotor from the Thorlabs plugin + return True + + def close(self): + """Terminate the communication protocol""" + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + if self.is_master: + # self.controller.your_method_to_terminate_the_communication() # when writing your own plugin replace this line + ... + + def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ + ## TODO for your custom plugin + if param.name() == 'axis': + self.axis_unit = self.controller.your_method_to_get_correct_axis_unit() + # do this only if you can and if the units are not known beforehand, for instance + # if the motors connected to the controller are of different type (mm, µm, nm, , etc...) + # see BrushlessDCMotor from the thorlabs plugin for an exemple + + elif param.name() == "a_parameter_you've_added_in_self.params": + self.controller.your_method_to_apply_this_param_change() + else: + pass + + def ini_stage(self, controller=None): + """Actuator communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case) + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True + """ + raise NotImplementedError # TODO when writing your own plugin remove this line and modify the ones below + if self.is_master: # is needed when controller is master + self.controller = PythonWrapperObjectOfYourInstrument(arg1, arg2, ...) # arguments for instantiation!) + initialized = self.controller.a_method_or_atttribute_to_check_if_init() # todo + # todo: enter here whatever is needed for your controller initialization and eventual + # opening of the communication channel + else: + self.controller = controller + initialized = True + + info = "Whatever info you want to log" + return info, initialized + + def move_abs(self, value: DataActuator): + """ Move the actuator to the absolute target defined by value + + Parameters + ---------- + value: (float) value of the absolute target positioning + """ + + value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here + self.target_value = value + value = self.set_position_with_scaling(value) # apply scaling if the user specified one + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + self.controller.your_method_to_set_an_absolute_value(value.value(self.axis_unit)) # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + def move_rel(self, value: DataActuator): + """ Move the actuator to the relative target actuator value defined by value + + Parameters + ---------- + value: (float) value of the relative target positioning + """ + value = self.check_bound(self.current_position + value) - self.current_position + self.target_value = value + self.current_position + value = self.set_position_relative_with_scaling(value) + + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + self.controller.your_method_to_set_a_relative_value(value.value(self.axis_unit)) # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + def move_home(self): + """Call the reference method of the controller""" + + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + self.controller.your_method_to_get_to_a_known_reference() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + def stop_motion(self): + """Stop the actuator and emits move_done signal""" + + ## TODO for your custom plugin + raise NotImplementedError # when writing your own plugin remove this line + self.controller.your_method_to_stop_positioning() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + +if __name__ == '__main__': + main(__file__) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Power.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Power.py deleted file mode 100644 index 5863245..0000000 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Power.py +++ /dev/null @@ -1,144 +0,0 @@ - -from typing import Union, List, Dict -from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, - main, DataActuatorType, DataActuator) - -from pymodaq_utils.utils import ThreadCommand # object used to send info back to the main thread -from pymodaq_gui.parameter import Parameter - -from pymodaq_plugins_teaching.hardware.spectrometer import Spectrometer - - - -class DAQ_Move_Power(DAQ_Move_base): - """ Instrument plugin class for an actuator. - - This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Move module through inheritance via - DAQ_Move_base. It makes a bridge between the DAQ_Move module and the Python wrapper of a particular instrument. - - Attributes: - ----------- - controller: object - The particular object that allow the communication with the hardware, in general a python wrapper around the - hardware library. - - """ - is_multiaxes = False - _axis_names: Union[List[str], Dict[str, int]] = [''] - _controller_units: Union[str, List[str]] = 'W' - _epsilon: Union[float, List[float]] = 0.1 - data_actuator_type = DataActuatorType.DataActuator - - params = [] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) - # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached - # the target value. It is the developer responsibility to put here a meaningful value - - def ini_attributes(self): - self.controller: Spectrometer = None - - #TODO declare here attributes you want/need to init with a default value - pass - - def get_actuator_value(self): - """Get the current value from the hardware with scaling conversion. - - Returns - ------- - float: The position obtained after scaling conversion. - """ - pos = DataActuator(data=self.controller.amplitude, - units=self.axis_unit) # when writing your own plugin replace this line - pos = self.get_position_with_scaling(pos) - return pos - - def user_condition_to_reach_target(self) -> bool: - """ Implement a condition for exiting the polling mechanism and specifying that the - target value has been reached - - Returns - ------- - bool: if True, PyMoDAQ considers the target value has been reached - """ - # TODO either delete this method if the usual polling is fine with you, but if need you can - # add here some other condition to be fullfilled either a completely new one or - # using or/and operations between the epsilon_bool and some other custom booleans - # for a usage example see DAQ_Move_brushlessMotor from the Thorlabs plugin - return True - - def close(self): - """Terminate the communication protocol""" - pass - - def commit_settings(self, param: Parameter): - """Apply the consequences of a change of value in the detector settings - - Parameters - ---------- - param: Parameter - A given parameter (within detector_settings) whose value has been changed by the user - """ - pass - - def ini_stage(self, controller=None): - """Actuator communication initialization - - Parameters - ---------- - controller: (object) - custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case) - - Returns - ------- - info: str - initialized: bool - False if initialization failed otherwise True - """ - - if self.is_master: - self.controller = Spectrometer() - else: - self.controller = controller - - self.controller.amplitude = 5.0 - - info = "Power control OK" - initialized = True - return info, initialized - - def move_abs(self, value: DataActuator): - """ Move the actuator to the absolute target defined by value - - Parameters - ---------- - value: (float) value of the absolute target positioning - """ - - value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here - self.target_value = value - value = self.set_position_with_scaling(value) # apply scaling if the user specified one - self.controller.amplitude = value.value() - - def move_rel(self, value: DataActuator): - """ Move the actuator to the relative target actuator value defined by value - - Parameters - ---------- - value: (float) value of the relative target positioning - """ - value = self.check_bound(self.current_position + value) - self.current_position - self.target_value = value + self.current_position - value = self.set_position_relative_with_scaling(value) - - self.move_abs(self.target_value) - - def move_home(self): - """Call the reference method of the controller""" - pass - - def stop_motion(self): - """Stop the actuator and emits move_done signal""" - pass - - -if __name__ == '__main__': - main(__file__) From 8a1b6767e9df2f9cad32b36f3d2ae0dee720bb52 Mon Sep 17 00:00:00 2001 From: jbrazard Date: Tue, 20 Jan 2026 15:14:52 +0100 Subject: [PATCH 2/9] initialization and moving initialize the monochromator modify the units move the monochromator --- .../daq_move_Monochromator.py | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index 25d721b..dc74094 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -9,15 +9,8 @@ # TODO: # Replace the following fake import with the import of the real Python wrapper of your instrument. Here we suppose that # the wrapper is in the hardware directory, but it could come from an external librairy like pylablib or pymeasure. -from pymodaq_plugins_template.hardware.python_wrapper_file_of_your_instrument import PythonWrapperObjectOfYourInstrument - -# TODO: -# (1) change the name of the following class to DAQ_Move_TheNameOfYourChoice -# (2) change the name of this file to daq_move_TheNameOfYourChoice ("TheNameOfYourChoice" should be the SAME -# for the class name and the file name.) -# (3) this file should then be put into the right folder, namely IN THE FOLDER OF THE PLUGIN YOU ARE DEVELOPING: -# pymodaq_plugins_my_plugin/daq_move_plugins +from pymodaq_plugins_teaching.hardware.spectrometer import Spectrometer class DAQ_Move_Monochromator(DAQ_Move_base): """ Instrument plugin class for an actuator. @@ -42,8 +35,8 @@ class DAQ_Move_Monochromator(DAQ_Move_base): """ is_multiaxes = False # TODO for your plugin set to True if this plugin is controlled for a multiaxis controller - _axis_names: Union[List[str], Dict[str, int]] = ['Axis1', 'Axis2'] # TODO for your plugin: complete the list - _controller_units: Union[str, List[str]] = 'mm' # TODO for your plugin: put the correct unit here, it could be + _axis_names: Union[List[str], Dict[str, int]] = [''] # TODO for your plugin: complete the list + _controller_units: Union[str, List[str]] = 'nm' # TODO for your plugin: put the correct unit here, it could be # TODO a single str (the same one is applied to all axes) or a list of str (as much as the number of axes) _epsilon: Union[float, List[float]] = 0.1 # TODO replace this by a value that is correct depending on your controller # TODO it could be a single float of a list of float (as much as the number of axes) @@ -58,7 +51,7 @@ class DAQ_Move_Monochromator(DAQ_Move_base): def ini_attributes(self): # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy # autocompletion - self.controller: PythonWrapperObjectOfYourInstrument = None + self.controller: Spectrometer = None #TODO declare here attributes you want/need to init with a default value pass @@ -71,8 +64,8 @@ def get_actuator_value(self): float: The position obtained after scaling conversion. """ ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line - pos = DataActuator(data=self.controller.your_method_to_get_the_actuator_value(), # when writing your own plugin replace this line + #raise NotImplementedError # when writing your own plugin remove this line + pos = DataActuator(data=self.controller.get_wavelength(), # when writing your own plugin replace this line units=self.axis_unit) pos = self.get_position_with_scaling(pos) return pos @@ -133,10 +126,11 @@ def ini_stage(self, controller=None): initialized: bool False if initialization failed otherwise True """ - raise NotImplementedError # TODO when writing your own plugin remove this line and modify the ones below + #raise NotImplementedError # TODO when writing your own plugin remove this line and modify the ones below if self.is_master: # is needed when controller is master - self.controller = PythonWrapperObjectOfYourInstrument(arg1, arg2, ...) # arguments for instantiation!) - initialized = self.controller.a_method_or_atttribute_to_check_if_init() # todo + self.controller = Spectrometer() # arguments for instantiation!) + initialized = (self.controller.open_communication()) + # a_method_or_atttribute_to_check_if_init()) todo # todo: enter here whatever is needed for your controller initialization and eventual # opening of the communication channel else: @@ -158,8 +152,7 @@ def move_abs(self, value: DataActuator): self.target_value = value value = self.set_position_with_scaling(value) # apply scaling if the user specified one ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line - self.controller.your_method_to_set_an_absolute_value(value.value(self.axis_unit)) # when writing your own plugin replace this line + self.controller.set_wavelength(value.value(self.axis_unit) ) # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) def move_rel(self, value: DataActuator): From dc20e57a68395c3c4d6f07506eafad2a8cb0d7c9 Mon Sep 17 00:00:00 2001 From: jbrazard Date: Tue, 20 Jan 2026 15:59:30 +0100 Subject: [PATCH 3/9] update find home and stop implement relative steps find home don't forget to hit Update stop --- .../daq_move_plugins/daq_move_Monochromator.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index dc74094..f764b23 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -167,24 +167,21 @@ def move_rel(self, value: DataActuator): value = self.set_position_relative_with_scaling(value) ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line - self.controller.your_method_to_set_a_relative_value(value.value(self.axis_unit)) # when writing your own plugin replace this line + self.controller.set_wavelength(value.value(self.axis_unit),set_type='rel' ) # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) def move_home(self): """Call the reference method of the controller""" ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line - self.controller.your_method_to_get_to_a_known_reference() # when writing your own plugin replace this line + self.controller.set_wavelength(532,'abs') # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) def stop_motion(self): """Stop the actuator and emits move_done signal""" ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line - self.controller.your_method_to_stop_positioning() # when writing your own plugin replace this line + self.controller.stop() # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) From 209502ddc2f08e2d5e576b16bb84d00068c1bdc7 Mon Sep 17 00:00:00 2001 From: jbrazard Date: Tue, 20 Jan 2026 17:41:55 +0100 Subject: [PATCH 4/9] photodiode add a viewer --- .../daq_move_Monochromator.py | 3 +- .../plugins_0D/daq_0Dviewer_Photodiode.py | 140 ++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Photodiode.py diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index f764b23..54e1586 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -87,9 +87,8 @@ def user_condition_to_reach_target(self) -> bool: def close(self): """Terminate the communication protocol""" ## TODO for your custom plugin - raise NotImplementedError # when writing your own plugin remove this line if self.is_master: - # self.controller.your_method_to_terminate_the_communication() # when writing your own plugin replace this line + self.controller.close_communication() # when writing your own plugin replace this line ... def commit_settings(self, param: Parameter): diff --git a/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Photodiode.py b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Photodiode.py new file mode 100644 index 0000000..b6ddddb --- /dev/null +++ b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Photodiode.py @@ -0,0 +1,140 @@ +import numpy as np + +from pymodaq_utils.utils import ThreadCommand +from pymodaq_data.data import DataToExport +from pymodaq_gui.parameter import Parameter + +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main +from pymodaq.utils.data import DataFromPlugins + +from pymodaq_plugins_teaching.hardware.spectrometer import Spectrometer + +class DAQ_0DViewer_Photodiode(DAQ_Viewer_base): + """ Instrument plugin class for a OD viewer. + + This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Viewer module through inheritance via + DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python wrapper of a particular instrument. + + TODO Complete the docstring of your plugin with: + * The set of instruments that should be compatible with this instrument plugin. + * With which instrument it has actually been tested. + * The version of PyMoDAQ during the test. + * The version of the operating system. + * Installation instructions: what manufacturer’s drivers should be installed to make it run? + + Attributes: + ----------- + controller: object + The particular object that allow the communication with the hardware, in general a python wrapper around the + hardware library. + + # TODO add your particular attributes here if any + + """ + params = comon_parameters+[ + ## TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage + ] + + def ini_attributes(self): + # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy + # autocompletion + self.controller: Spectrometer = None + + #TODO declare here attributes you want/need to init with a default value + pass + + def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ + ## TODO for your custom plugin + if param.name() == "a_parameter_you've_added_in_self.params": + self.controller.your_method_to_apply_this_param_change() # when writing your own plugin replace this line +# elif ... + ## + + def ini_detector(self, controller=None): + """Detector communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller + (Master case) + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True + """ + + + if self.is_master: + self.controller = Spectrometer() #instantiate you driver with whatever arguments are needed + initialized = self.controller.open_communication() # TODO + else: + self.controller = controller + initialized = True + + #TODO for your custom plugin (optional) initialize viewers panel with the future type of data + self.dte_signal_temp.emit(DataToExport(name='myplugin', + data=[DataFromPlugins(name='Mock1', + data=[np.array([0]), np.array([0])], + dim='Data0D', + labels=['Mock1', 'label2'])])) + + info = "Whatever info you want to log" + return info, initialized + + def close(self): + """Terminate the communication protocol""" + ## TODO for your custom plugin + if self.is_master: + self.controller.close_communication() # when writing your own plugin replace this line + + + def grab_data(self, Naverage=1, **kwargs): + """Start a grab from the detector + + Parameters + ---------- + Naverage: int + Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to + True in class preamble and you should code this implementation) + kwargs: dict + others optionals arguments + """ + ## TODO for your custom plugin: you should choose EITHER the synchrone or the asynchrone version following + + # synchrone version (blocking function) + + data_tot = self.controller.grab_monochromator() + self.dte_signal.emit(DataToExport(name='Photodiode', + data=[DataFromPlugins(name='Photodiode', data=[data_tot], + dim='Data0D', labels=['Intensity'],units='volts')])) + ######################################################### + + + def callback(self): + """optional asynchrone method called when the detector has finished its acquisition of data""" + data_tot = self.controller.grab_monochromator() + self.dte_signal.emit(DataToExport(name='myplugin', + data=[DataFromPlugins(name='Mock1', data=data_tot, + dim='Data0D', labels=['dat0', 'data1'])])) + + def stop(self): + """Stop the current grab hardware wise if necessary""" + ## TODO for your custom plugin + self.controller.stop() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + ############################## + return '' + + +if __name__ == '__main__': + main(__file__) From 920dc35a06bd9b30acd64d532fdbbeffc546ddd7 Mon Sep 17 00:00:00 2001 From: jbrazard Date: Wed, 21 Jan 2026 10:43:52 +0100 Subject: [PATCH 5/9] add a parameter Add the tau parameter --- .../daq_move_plugins/daq_move_Monochromator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index 54e1586..722712b 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -43,7 +43,10 @@ class DAQ_Move_Monochromator(DAQ_Move_base): data_actuator_type = DataActuatorType.DataActuator # wether you use the new data style for actuator otherwise set this # as DataActuatorType.float (or entirely remove the line) - params = [ # TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage + params = [ + {'Title':'Tau (ms)', 'name':'tau', 'type':'float','value':1234., + 'suffix':'ms','visible':True,'readonly':False} + # TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached # the target value. It is the developer responsibility to put here a meaningful value From e9cf40f439b71f2fcf05a5432cc5fbc6059e2b8f Mon Sep 17 00:00:00 2001 From: jbrazard Date: Wed, 21 Jan 2026 11:50:19 +0100 Subject: [PATCH 6/9] add the parameter tau and grating --- .../daq_move_Monochromator.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index 722712b..ddeb7b5 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -45,7 +45,9 @@ class DAQ_Move_Monochromator(DAQ_Move_base): params = [ {'Title':'Tau (ms)', 'name':'tau', 'type':'float','value':1234., - 'suffix':'ms','visible':True,'readonly':False} + 'suffix':'ms','visible':True,'readonly':False}, + {'Title':'Grating', 'name':'grating', 'type':'list','limits':['G300','G1200'], + 'value':Spectrometer.gratings,'visible':True,'readonly':False}, # TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached @@ -103,14 +105,8 @@ def commit_settings(self, param: Parameter): A given parameter (within detector_settings) whose value has been changed by the user """ ## TODO for your custom plugin - if param.name() == 'axis': - self.axis_unit = self.controller.your_method_to_get_correct_axis_unit() - # do this only if you can and if the units are not known beforehand, for instance - # if the motors connected to the controller are of different type (mm, µm, nm, , etc...) - # see BrushlessDCMotor from the thorlabs plugin for an exemple - - elif param.name() == "a_parameter_you've_added_in_self.params": - self.controller.your_method_to_apply_this_param_change() + if param.name() == 'tau': + self.controller.tau = param.value()/1000 else: pass @@ -135,10 +131,17 @@ def ini_stage(self, controller=None): # a_method_or_atttribute_to_check_if_init()) todo # todo: enter here whatever is needed for your controller initialization and eventual # opening of the communication channel + #self.controller.tau + + self.settings.child('tau').setValue(self.controller.tau*1000) + + else: self.controller = controller initialized = True + + info = "Whatever info you want to log" return info, initialized From b5c64242e9c1d14165ac77ab32897bc6fe04764f Mon Sep 17 00:00:00 2001 From: jbrazard Date: Wed, 21 Jan 2026 12:02:32 +0100 Subject: [PATCH 7/9] update on the grating --- .../daq_move_plugins/daq_move_Monochromator.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py index ddeb7b5..81bfd71 100644 --- a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -46,8 +46,8 @@ class DAQ_Move_Monochromator(DAQ_Move_base): params = [ {'Title':'Tau (ms)', 'name':'tau', 'type':'float','value':1234., 'suffix':'ms','visible':True,'readonly':False}, - {'Title':'Grating', 'name':'grating', 'type':'list','limits':['G300','G1200'], - 'value':Spectrometer.gratings,'visible':True,'readonly':False}, + {'Title':'Grating', 'name':'grating', 'type':'list','limits':Spectrometer.gratings, + 'value':Spectrometer.gratings[0],'visible':True,'readonly':False}, # TODO for your custom plugin: elements to be added here as dicts in order to control your custom stage ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached @@ -107,6 +107,10 @@ def commit_settings(self, param: Parameter): ## TODO for your custom plugin if param.name() == 'tau': self.controller.tau = param.value()/1000 + + elif param.name() == 'grating': + self.controller.grating = param.value() + else: pass @@ -133,13 +137,18 @@ def ini_stage(self, controller=None): # opening of the communication channel #self.controller.tau - self.settings.child('tau').setValue(self.controller.tau*1000) + else: self.controller = controller initialized = True + if initialized: + self.settings.child('tau').setValue(self.controller.tau * 1000) + self.settings.child('grating').setLimits(self.controller.gratings) + self.settings.child('grating').setValue(self.controller.grating) + info = "Whatever info you want to log" From 5e40119f10619305d15ae8dcf6e20e9a0f992dbd Mon Sep 17 00:00:00 2001 From: jbrazard Date: Wed, 21 Jan 2026 12:36:38 +0100 Subject: [PATCH 8/9] Create daq_1Dviewer_CCD.py create a CCD detector --- .../plugins_1D/daq_1Dviewer_CCD.py | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_1D/daq_1Dviewer_CCD.py diff --git a/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_1D/daq_1Dviewer_CCD.py b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_1D/daq_1Dviewer_CCD.py new file mode 100644 index 0000000..3d586a8 --- /dev/null +++ b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_1D/daq_1Dviewer_CCD.py @@ -0,0 +1,174 @@ +import numpy as np + +from pymodaq_utils.utils import ThreadCommand +from pymodaq_data.data import DataToExport, Axis +from pymodaq_gui.parameter import Parameter + +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main +from pymodaq.utils.data import DataFromPlugins + +# TODO: +# Replace the following fake import with the import of the real Python wrapper of your instrument. Here we suppose that +# the wrapper is in the hardware directory, but it could come from an external librairy like pylablib or pymeasure. + +from pymodaq_plugins_teaching.hardware.spectrometer import Spectrometer + + + +# TODO: +# (1) change the name of the following class to DAQ_1DViewer_TheNameOfYourChoice +# (2) change the name of this file to daq_1Dviewer_TheNameOfYourChoice ("TheNameOfYourChoice" should be the SAME +# for the class name and the file name.) +# (3) this file should then be put into the right folder, namely IN THE FOLDER OF THE PLUGIN YOU ARE DEVELOPING: +# pymodaq_plugins_my_plugin/daq_viewer_plugins/plugins_1D + + +class DAQ_1DViewer_CCD(DAQ_Viewer_base): + """ Instrument plugin class for a 1D viewer. + + This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Viewer module through inheritance via + DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python wrapper of a particular instrument. + + TODO Complete the docstring of your plugin with: + * The set of instruments that should be compatible with this instrument plugin. + * With which instrument it has actually been tested. + * The version of PyMoDAQ during the test. + * The version of the operating system. + * Installation instructions: what manufacturer’s drivers should be installed to make it run? + + Attributes: + ----------- + controller: object + The particular object that allow the communication with the hardware, in general a python wrapper around the + hardware library. + + # TODO add your particular attributes here if any + + """ + params = comon_parameters+[ + ## TODO for your custom plugin + # elements to be added here as dicts in order to control your custom stage + ############ + ] + + def ini_attributes(self): + # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy + # autocompletion + self.controller: Spectrometer = None + + # TODO declare here attributes you want/need to init with a default value + + self.x_axis = None + + def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ + ## TODO for your custom plugin + if param.name() == "a_parameter_you've_added_in_self.params": + self.controller.your_method_to_apply_this_param_change() +# elif ... + ## + + def ini_detector(self, controller=None): + """Detector communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller + (Master case) + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True + """ + + + if self.is_master: + self.controller = Spectrometer() #instantiate you driver with whatever arguments are needed + self.controller.open_communication() # call eventual methods + initialized = self.controller.open_communication() # TODO + else: + self.controller = controller + initialized = True + + ## TODO for your custom plugin + # get the x_axis (you may want to to this also in the commit settings if x_axis may have changed + #data_x_axis = self.controller.your_method_to_get_the_x_axis() # if possible + #self.x_axis = Axis(data=data_x_axis, label='', units='nm', index=0) + + # TODO for your custom plugin. Initialize viewers pannel with the future type of data + #self.dte_signal_temp.emit(DataToExport(name='myplugin', + # data=[DataFromPlugins(name='Mock1', + # data=[np.array([0., 0., ...]), + # np.array([0., 0., ...])], + # dim='Data1D', labels=['Mock1', 'label2'], + # axes=[self.x_axis])])) + + info = "Whatever info you want to log" + return info, initialized + + def close(self): + """Terminate the communication protocol""" + ## TODO for your custom plugin + + if self.is_master: + self.controller.close_communication() + # self.controller.your_method_to_terminate_the_communication() # when writing your own plugin replace this line + ... + + def grab_data(self, Naverage=1, **kwargs): + """Start a grab from the detector + + Parameters + ---------- + Naverage: int + Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to + True in class preamble and you should code this implementation) + kwargs: dict + others optionals arguments + """ + ## TODO for your custom plugin: you should choose EITHER the synchrone or the asynchrone version following + + ##synchrone version (blocking function) + data_1D = self.controller.grab_spectrum() + wavelength_array=self.controller.get_wavelength_axis() + + axis= Axis(label='Wavelength', units= "nm", data = wavelength_array,index=0) + + self.dte_signal.emit(DataToExport('myplugin', + data=[DataFromPlugins(name='CCD', data=data_1D, + dim='Data1D', labels=['Intensity'], + axes=[axis])])) + + ##asynchrone version (non-blocking function with callback) + #self.controller.grab_monochromator() + ######################################################### + + + def callback(self): + """optional asynchrone method called when the detector has finished its acquisition of data""" + data_tot = self.controller.grab_monochromator() + self.dte_signal.emit(DataToExport('myplugin', + data=[DataFromPlugins(name='Mock1', data=data_tot, + dim='Data1D', labels=['dat0', 'data1'])])) + + def stop(self): + """Stop the current grab hardware wise if necessary""" + ## TODO for your custom plugin + + self.controller.stop() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + ############################## + return '' + + +if __name__ == '__main__': + main(__file__) From 5980e965f3075e1c37de37fc5e022cc616b0bd0e Mon Sep 17 00:00:00 2001 From: jbrazard Date: Wed, 21 Jan 2026 16:43:17 +0100 Subject: [PATCH 9/9] Beam profiler --- .../plugins_2D/daq_2Dviewer_BeamProfiler.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_2D/daq_2Dviewer_BeamProfiler.py diff --git a/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_2D/daq_2Dviewer_BeamProfiler.py b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_2D/daq_2Dviewer_BeamProfiler.py new file mode 100644 index 0000000..a5df66d --- /dev/null +++ b/src/pymodaq_plugins_teaching/daq_viewer_plugins/plugins_2D/daq_2Dviewer_BeamProfiler.py @@ -0,0 +1,73 @@ +import numpy as np +from pymodaq.utils.data import DataFromPlugins +from qtpy.QtCore import QThread, Slot, QRectF +from qtpy import QtWidgets +import laserbeamsize as lbs + +from pymodaq_plugins_mockexamples.daq_viewer_plugins.plugins_2D.daq_2Dviewer_BSCamera import DAQ_2DViewer_BSCamera + +from pymodaq_utils.utils import ThreadCommand +from pymodaq_data.data import DataToExport, Axis +from pymodaq_gui.parameter import Parameter + +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main + +from pymodaq_plugins_mockexamples.daq_viewer_plugins.plugins_2D.daq_2Dviewer_BSCamera import DAQ_2DViewer_BSCamera + + +# TODO: +# (1) change the name of the following class to DAQ_2DViewer_TheNameOfYourChoice +# (2) change the name of this file to daq_2Dviewer_TheNameOfYourChoice ("TheNameOfYourChoice" should be the SAME +# for the class name and the file name.) +# (3) this file should then be put into the right folder, namely IN THE FOLDER OF THE PLUGIN YOU ARE DEVELOPING: +# pymodaq_plugins_my_plugin/daq_viewer_plugins/plugins_2D + + +class DAQ_2DViewer_BeamProfiler(DAQ_2DViewer_BSCamera): + live_mode_available = False + + params = DAQ_2DViewer_BSCamera.params + [ + {'title': 'beam', 'name': 'beam', 'type': 'led', 'value': True}, + {'title': 'position', 'name': 'position', 'type': 'bool', 'value': True}, + {'title': 'width', 'name': 'width', 'type': 'bool', 'value': True}, + {'title': 'Beam Angle (deg)', 'name': 'phi2', 'type': 'bool', 'value': True }, + + ] + + + def grab_data(self, Naverage=1, **kwargs): + """Start a grab from the detector + + #########################################################""" + data = self.average_data(Naverage) + beam = data[0].data[0] + + x,y,d_major,d_minor,phi=lbs.beam_size(beam) + #package things + + dte = DataToExport('BeamProfiler', + data=[ + DataFromPlugins('Beam', + data=[beam], + labels=['Raw beam'], + do_plot=self.settings['beam']), + DataFromPlugins('Position', + data=[np.atleast_1d(x), np.atleast_1d(y)], + labels=['x','y'], + do_plot=self.settings['position']), + DataFromPlugins('Size', + data=[np.atleast_1d(d_major), np.atleast_1d(d_minor)], + labels=['Major', 'Minor'], + do_plot=self.settings['width']), + DataFromPlugins('Angle', + data=[np.atleast_1d(phi)], + labels=['angle'], + do_plot=self.settings['phi2']), + ]) + self.dte_signal.emit(dte) + + # return super().grab_data(Naverage=Naverage, **kwargs) + + +if __name__ == '__main__': + main(__file__)