25032024 lightfield pymodaq plugin

This commit is contained in:
Sébastien Quistrebert 2024-03-25 13:48:51 +01:00
parent fd977cd831
commit 49bd905d9b
30 changed files with 1296 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Sebastien Weber <sebastien.weber@cemes.fr>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

3
MANIFEST.in Normal file
View file

@ -0,0 +1,3 @@
include README.rst
include LICENSE
graft src

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

24
plugin_info.toml Normal file
View file

@ -0,0 +1,24 @@
## To modify by developer(s) of the plugin
[plugin-info]
SHORT_PLUGIN_NAME = 'lightfield' #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_template' #to modify
description = 'some word about your plugin'
author = 'Sébastien Quistrebert'
author-email = 'Author email'
license = 'MIT'
[plugin-install]
#packages required for your plugin:
packages-required = ['pymodaq>=4.1.0']
[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 = false # true if plugins contains dashboard extensions
models = false # 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)

4
setup.py Normal file
View file

@ -0,0 +1,4 @@
from pymodaq.resources.setup_plugin import setup
from pathlib import Path
setup(Path(__file__).parent)

View file

@ -0,0 +1,8 @@
from pathlib import Path
from pymodaq.utils.logger import set_logger # to be imported by other modules.
from .utils import Config
config = Config()
with open(str(Path(__file__).parent.joinpath('resources/VERSION')), 'r') as fvers:
__version__ = fvers.read().strip()

View file

@ -0,0 +1,13 @@
import importlib
from pathlib import Path
from .. import set_logger
logger = set_logger('move_plugins', add_to_console=False)
for path in Path(__file__).parent.iterdir():
try:
if '__init__' not in str(path):
importlib.import_module('.' + path.stem, __package__)
except Exception as e:
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
pass

View file

@ -0,0 +1,165 @@
from pymodaq.control_modules.move_utility_classes import DAQ_Move_base, comon_parameters_fun, main, DataActuatorType,\
DataActuator # common set of parameters for all actuators
from pymodaq.utils.daq_utils import ThreadCommand # object used to send info back to the main thread
from pymodaq.utils.parameter import Parameter
class PythonWrapperOfYourInstrument:
# TODO Replace this fake class with the import of the real python wrapper of your instrument
pass
# 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_Template(DAQ_Move_base):
""" Instrument plugin class for an actuator.
This object inherits all functionalities to communicate with PyMoDAQs 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 manufacturers 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
"""
_controller_units = 'whatever' # TODO for your plugin: put the correct unit here
is_multiaxes = False # TODO for your plugin set to True if this plugin is controlled for a multiaxis controller
_axis_names = ['Axis1', 'Axis2'] # TODO for your plugin: complete the list
_epsilon = 0.1 # TODO replace this by a value that is correct depending on your controller
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: PythonWrapperOfYourInstrument = 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 NotImplemented # 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
pos = self.get_position_with_scaling(pos)
return pos
def close(self):
"""Terminate the communication protocol"""
## TODO for your custom plugin
raise NotImplemented # when writing your own plugin remove this line
# 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() == "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 NotImplemented # TODO when writing your own plugin remove this line and modify the one below
self.controller = self.ini_stage_init(old_controller=controller,
new_controller=PythonWrapperOfYourInstrument())
info = "Whatever info you want to log"
initialized = self.controller.a_method_or_atttribute_to_check_if_init() # todo
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 NotImplemented # when writing your own plugin remove this line
self.controller.your_method_to_set_an_absolute_value(value.value()) # 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 NotImplemented # when writing your own plugin remove this line
self.controller.your_method_to_set_a_relative_value(value.value()) # 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 NotImplemented # 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 NotImplemented # 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__)

View file

@ -0,0 +1,3 @@

View file

@ -0,0 +1,13 @@
import importlib
from pathlib import Path
from ... import set_logger
logger = set_logger('viewer0D_plugins', add_to_console=False)
for path in Path(__file__).parent.iterdir():
try:
if '__init__' not in str(path):
importlib.import_module('.' + path.stem, __package__)
except Exception as e:
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
pass

View file

@ -0,0 +1,148 @@
import numpy as np
from pymodaq.utils.daq_utils import ThreadCommand
from pymodaq.utils.data import DataFromPlugins, DataToExport
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
from pymodaq.utils.parameter import Parameter
class PythonWrapperOfYourInstrument:
# TODO Replace this fake class with the import of the real python wrapper of your instrument
pass
# TODO:
# (1) change the name of the following class to DAQ_0DViewer_TheNameOfYourChoice
# (2) change the name of this file to daq_0Dviewer_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_0D
class DAQ_0DViewer_Template(DAQ_Viewer_base):
""" Instrument plugin class for a OD viewer.
This object inherits all functionalities to communicate with PyMoDAQs 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 manufacturers 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: PythonWrapperOfYourInstrument = 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
"""
raise NotImplemented # TODO when writing your own plugin remove this line and modify the one below
self.ini_detector_init(old_controller=controller,
new_controller=PythonWrapperOfYourInstrument())
# 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"
initialized = self.controller.a_method_or_atttribute_to_check_if_init() # TODO
return info, initialized
def close(self):
"""Terminate the communication protocol"""
## TODO for your custom plugin
raise NotImplemented # when writing your own plugin remove this line
# 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)
raise NotImplemented # when writing your own plugin remove this line
data_tot = self.controller.your_method_to_start_a_grab_snap()
self.dte_signal.emit(DataToExport(name='myplugin',
data=[DataFromPlugins(name='Mock1', data=data_tot,
dim='Data0D', labels=['dat0', 'data1'])]))
#########################################################
# asynchrone version (non-blocking function with callback)
raise NotImplemented # when writing your own plugin remove this line
self.controller.your_method_to_start_a_grab_snap(self.callback) # when writing your own plugin replace this line
#########################################################
def callback(self):
"""optional asynchrone method called when the detector has finished its acquisition of data"""
data_tot = self.controller.your_method_to_get_data_from_buffer()
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
raise NotImplemented # when writing your own plugin remove this line
self.controller.your_method_to_stop_acquisition() # 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__)

View file

@ -0,0 +1,13 @@
import importlib
from pathlib import Path
from ... import set_logger
logger = set_logger('viewer1D_plugins', add_to_console=False)
for path in Path(__file__).parent.iterdir():
try:
if '__init__' not in str(path):
importlib.import_module('.' + path.stem, __package__)
except Exception as e:
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
pass

View file

@ -0,0 +1,162 @@
import numpy as np
from pymodaq.utils.daq_utils import ThreadCommand, get_plugins
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
from pymodaq.utils.parameter import Parameter
from pymodaq_plugins_lightfield.hardware.my_lightfield_library import PILF
# 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_lightfield(DAQ_Viewer_base):
""" Instrument plugin class for a 1D viewer.
This object inherits all functionalities to communicate with PyMoDAQs 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 manufacturers 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+[{'title' : 'Lightfield Params', 'name' : 'lightfield_params', 'type' : 'group', 'children' : [
{'title' : 'Camera Shutter', 'name' : 'camera_shutter', 'type' : 'bool', 'value' : False},
{'title' : 'Number of pairs of spectra', 'name' : 'kc_pairs', 'type' : 'int', 'value' : 500}
]}
]
def ini_attributes(self):
if self.controller is None:
self.controller = {}
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
"""
if param.name() == "camera_shutter":
self.controller['lightfield'].open_shutter(param.value())
elif param.name() == "kc_pairs":
self.controller['lightfield'].setNframes(param.value()*2)
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 controller is None:
controller = {}
new_controller = controller.copy()
new_controller['lightfield'] = PILF()
self.ini_detector_init(old_controller=controller, new_controller=new_controller)
## 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['lightfield'].get_x_axis() # if possible
self.x_axis = Axis(data=data_x_axis, label='', units='', index=0)
# TODO for your custom plugin. Initialize viewers pannel with the future type of data
data0 = np.zeros(1024)
self.dte_signal_temp.emit(DataToExport(name='lightfield',
data=[DataFromPlugins(name='Spectra',
data=[data0, data0],
dim='Data1D', labels=['TA', 'I_avg'],
axes=[self.x_axis])]))
err = self.controller['lightfield'].err_init()
if not err:
info = ""
initialized = True
else:
info = err
initialized = False
return info, initialized
def close(self):
"""Terminate the communication protocol"""
self.controller['lightfield'].close() # 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)
image_array = self.capture_spectra()
I_ON = image_array[:, ::2]
I_OFF = image_array[:, 1::2]
I_ON, I_OFF = I_ON.astype(np.int64), I_OFF.astype(np.int64)
TA = -1e3*np.mean(np.log(I_ON/I_OFF), axis = 1)
I_avg = np.mean(I_ON + I_OFF, axis = 1)/2
self.dte_signal.emit(DataToExport('lightfield',
data=[DataFromPlugins(name='Spectra',
data=[TA, I_avg],
dim='Data1D', labels=['TA', 'I_avg'],
axes=[self.x_axis])]))
##asynchrone version (non-blocking function with callback)
#self.controller.your_method_to_start_a_grab_snap(self.callback)
#########################################################
def capture_spectra(self):
"""Single acquisition according the specified experiment settings"""
return self.controller['lightfield'].capture_spectra()
def callback(self):
"""optional asynchrone method called when the detector has finished its acquisition of data"""
data_tot = self.controller['lightfield'].your_method_to_get_data_from_buffer()
self.dte_signal.emit(DataToExport('lightfield',
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['lightfield'].stop() # when writing your own plugin replace this line
self.emit_status(ThreadCommand('Update_Status', ['Lightfield plugin stopped']))
##############################
return ''
def get_controller_class(self):
return PILF
if __name__ == '__main__':
main(__file__)

View file

@ -0,0 +1,14 @@
import importlib
from pathlib import Path
from ... import set_logger
logger = set_logger('viewer2D_plugins', add_to_console=False)
for path in Path(__file__).parent.iterdir():
try:
if '__init__' not in str(path):
importlib.import_module('.' + path.stem, __package__)
except Exception as e:
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
pass

View file

@ -0,0 +1,156 @@
from pymodaq.utils.daq_utils import ThreadCommand
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
from pymodaq.utils.parameter import Parameter
class PythonWrapperOfYourInstrument:
# TODO Replace this fake class with the import of the real python wrapper of your instrument
pass
# 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_Template(DAQ_Viewer_base):
""" Instrument plugin class for a 2D viewer.
This object inherits all functionalities to communicate with PyMoDAQs 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 manufacturers 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: PythonWrapperOfYourInstrument = None
# TODO declare here attributes you want/need to init with a default value
self.x_axis = None
self.y_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
"""
raise NotImplemented # TODO when writing your own plugin remove this line and modify the one below
self.ini_detector_init(old_controller=controller,
new_controller=PythonWrapperOfYourInstrument())
## 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='', index=1)
# get the y_axis (you may want to to this also in the commit settings if y_axis may have changed
data_y_axis = self.controller.your_method_to_get_the_y_axis() # if possible
self.y_axis = Axis(data=data_y_axis, label='', units='', index=0)
## TODO for your custom plugin. Initialize viewers pannel with the future type of data
self.dte_signal_temp.emit(DataToExport('myplugin',
data=[DataFromPlugins(name='Mock1', data=["2D numpy array"],
dim='Data2D', labels=['dat0'],
axes=[self.x_axis, self.y_axis]), ]))
info = "Whatever info you want to log"
initialized = True
return info, initialized
def close(self):
"""Terminate the communication protocol"""
## TODO for your custom plugin
raise NotImplemented # when writing your own plugin remove this line
# 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_tot = self.controller.your_method_to_start_a_grab_snap()
self.dte_signal.emit(DataToExport('myplugin',
data=[DataFromPlugins(name='Mock1', data=data_tot,
dim='Data2D', labels=['label1'],
x_axis=self.x_axis,
y_axis=self.y_axis), ]))
##asynchrone version (non-blocking function with callback)
self.controller.your_method_to_start_a_grab_snap(self.callback)
#########################################################
def callback(self):
"""optional asynchrone method called when the detector has finished its acquisition of data"""
data_tot = self.controller.your_method_to_get_data_from_buffer()
self.dte_signal.emit(DataToExport('myplugin',
data=[DataFromPlugins(name='Mock1', data=data_tot,
dim='Data2D', labels=['label1'],
x_axis=self.x_axis,
y_axis=self.y_axis), ]))
def stop(self):
"""Stop the current grab hardware wise if necessary"""
## TODO for your custom plugin
raise NotImplemented # when writing your own plugin remove this line
self.controller.your_method_to_stop_acquisition() # 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__)

View file

@ -0,0 +1,13 @@
import importlib
from pathlib import Path
from ... import set_logger
logger = set_logger('viewerND_plugins', add_to_console=False)
for path in Path(__file__).parent.iterdir():
try:
if '__init__' not in str(path):
importlib.import_module('.' + path.stem, __package__)
except Exception as e:
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
pass

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Created the 01/06/2023
@author: Sebastien Weber
"""

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Created the 01/06/2023
@author: Sebastien Weber
"""

View file

@ -0,0 +1,195 @@
from pymodaq.utils import gui_utils as gutils
from pymodaq.utils import daq_utils as utils
from pyqtgraph.parametertree import Parameter, ParameterTree
from pymodaq.utils.parameter import pymodaq_ptypes
from qtpy import QtWidgets, QtCore
from pymodaq.utils.plotting.data_viewers.viewer1D import Viewer1D
from pymodaq.utils.plotting.data_viewers.viewer2D import Viewer2D
config = utils.load_config()
logger = utils.set_logger(utils.get_module_name(__file__))
EXTENSION_NAME = 'MY_EXTENSION_NAME'
CLASS_NAME = 'MyExtension'
class MyExtension(gutils.CustomApp):
# list of dicts enabling the settings tree on the user interface
params = [
{'title': 'Main settings:', 'name': 'main_settings', 'type': 'group', 'children': [
{'title': 'Save base path:', 'name': 'base_path', 'type': 'browsepath',
'value': config['data_saving']['h5file']['save_path']},
{'title': 'File name:', 'name': 'target_filename', 'type': 'str', 'value': "", 'readonly': True},
{'title': 'Date:', 'name': 'date', 'type': 'date', 'value': QtCore.QDate.currentDate()},
{'title': 'Do something, such as showing data:', 'name': 'do_something', 'type': 'bool', 'value': False},
{'title': 'Something done:', 'name': 'something_done', 'type': 'led', 'value': False, 'readonly': True},
{'title': 'Infos:', 'name': 'info', 'type': 'text', 'value': ""},
{'title': 'push:', 'name': 'push', 'type': 'bool_push', 'value': False}
]},
{'title': 'Other settings:', 'name': 'other_settings', 'type': 'group', 'children': [
{'title': 'List of stuffs:', 'name': 'list_stuff', 'type': 'list', 'value': 'first',
'limits': ['first', 'second', 'third'], 'tip': 'choose a stuff from the list'},
{'title': 'List of integers:', 'name': 'list_int', 'type': 'list', 'value': 0,
'limits': [0, 256, 512], 'tip': 'choose a stuff from this int list'},
{'title': 'one integer:', 'name': 'an_integer', 'type': 'int', 'value': 500, },
{'title': 'one float:', 'name': 'a_float', 'type': 'float', 'value': 2.7, },
]},
]
def __init__(self, dockarea, dashboard):
super().__init__(dockarea, dashboard)
self.setup_ui()
def connect_things(self):
pass
def setup_docks(self):
"""
to be subclassed to setup the docks layout
for instance:
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
"""
self.docks['settings'] = gutils.Dock('Settings')
self.dockarea.addDock(self.docks['settings'])
self.docks['settings'].addWidget(self.settings_tree)
self.docks['modmanager'] = gutils.Dock('Module Manager')
self.dockarea.addDock(self.docks['modmanager'], 'right', self.docks['settings'])
self.docks['modmanager'].addWidget(self.modules_manager.settings_tree)
self.docks['viewer1D'] = gutils.Dock('Viewers')
self.dockarea.addDock(self.docks['viewer1D'], 'right', self.docks['modmanager'])
self.docks['viewer2D'] = gutils.Dock('Viewers')
self.dockarea.addDock(self.docks['viewer2D'], 'bottom', self.docks['viewer1D'])
widg = QtWidgets.QWidget()
self.viewer1D = Viewer1D(widg)
self.docks['viewer1D'].addWidget(widg)
widg1 = QtWidgets.QWidget()
self.viewer2D = Viewer2D(widg1)
self.docks['viewer2D'].addWidget(widg1)
def setup_menu(self):
'''
to be subclassed
create menu for actions contained into the self.actions_manager, for instance:
For instance:
file_menu = self.menubar.addMenu('File')
self.actions_manager.affect_to('load', file_menu)
self.actions_manager.affect_to('save', file_menu)
file_menu.addSeparator()
self.actions_manager.affect_to('quit', file_menu)
'''
pass
def value_changed(self, param):
''' to be subclassed for actions to perform when one of the param's value in self.settings is changed
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
'''
if param.name() == 'do_something':
if param.value():
self.modules_manager.det_done_signal.connect(self.show_data)
else:
self.modules_manager.det_done_signal.disconnect()
def param_deleted(self, param):
''' to be subclassed for actions to perform when one of the param in self.settings has been deleted
Parameters
----------
param: (Parameter) the parameter that has been deleted
'''
raise NotImplementedError
def child_added(self, param):
''' to be subclassed for actions to perform when a param has been added in self.settings
Parameters
----------
param: (Parameter) the parameter that has been deleted
'''
raise NotImplementedError
def setup_actions(self):
pass
def show_data(self, data_all):
data1D = []
data2D = []
labels1D = []
labels2D = []
dims = ['data1D', 'data2D']
for det in data_all:
for dim in dims:
if len(data_all[det][dim]) != 0:
for channel in data_all[det][dim]:
if dim == 'data1D':
labels1D.append(channel)
data1D.append(data_all[det][dim][channel]['data'])
else:
labels2D.append(channel)
data2D.append(data_all[det][dim][channel]['data'])
self.viewer1D.show_data(data1D)
self.viewer2D.setImage(*data2D[:min(3, len(data2D))])
def main():
import sys
from pymodaq.dashboard import DashBoard
from pathlib import Path
app = QtWidgets.QApplication(sys.argv)
mainwindow = QtWidgets.QMainWindow()
dockarea = gutils.DockArea()
mainwindow.setCentralWidget(dockarea)
# init the dashboard
mainwindow_dash = QtWidgets.QMainWindow()
area_dash = gutils.DockArea()
mainwindow_dash.setCentralWidget(area_dash)
dashboard = DashBoard(area_dash)
file = Path(utils.get_set_preset_path()).joinpath(f"{config['presets']['default_preset_for_scan']}.xml")
if file.exists():
dashboard.set_preset_mode(file)
else:
msgBox = QtWidgets.QMessageBox()
msgBox.setText(f"The default file specified in the configuration file does not exists!\n"
f"{file}\n"
f"Impossible to load the DAQ_Scan Module")
msgBox.setStandardButtons(msgBox.Ok)
ret = msgBox.exec()
prog = MyExtension(dockarea, dashboard)
mainwindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

View file

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Feb 5 17:09:01 2024
Sébastien Quistrebert
"""
import pyqtgraph as pg
# Import the .NET class library
import clr, ctypes
# Import python sys module
import sys, os
# numpy import
import numpy as np
# Import c compatible List and String
from System import String
from System.Collections.Generic import List
from System.Runtime.InteropServices import Marshal
from System.Runtime.InteropServices import GCHandle, GCHandleType
# Add needed dll references
sys.path.append(os.environ['LIGHTFIELD_ROOT'])
sys.path.append(os.environ['LIGHTFIELD_ROOT']+"\\AddInViews")
clr.AddReference('PrincetonInstruments.LightFieldViewV5')
clr.AddReference('PrincetonInstruments.LightField.AutomationV5')
clr.AddReference('PrincetonInstruments.LightFieldAddInSupportServices')
# PI imports
from PrincetonInstruments.LightField.Automation import *
from PrincetonInstruments.LightField.AddIns import *
class PILF():
def __init__(self):
self._auto = Automation(True, List[String]())
# Get LightField Application object
self._application = self._auto.LightFieldApplication
# Get experiment object
self._experiment = self._application.Experiment
#Close the shutter for security
self._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysClosed')
self._auto.LightFieldClosing += self.lightField_closing
def err_init(self):
camera = None
err = False
# Find connected device
for device in self._experiment.ExperimentDevices:
if (device.Type == DeviceType.Camera and self._experiment.IsReadyToRun):
camera = device
if (camera == None):
err = "No camera detected."
if (not self._experiment.IsReadyToRun):
err = "Experiment not ready"
return err
def capture_spectra(self):
if self._experiment.get_Name() == 'LABVIEW_20180912_64lignes_3kHz_1zone' and self._experiment.IsReadyToRun:
frames = self._experiment.GetValue(ExperimentSettings.AcquisitionFramesToStore)
image_array = np.zeros((1024,frames))
dataset = self._experiment.Capture(frames)
# Stop processing if we do not have all frames
if (dataset.Frames != frames):
# Clean up the image data set
dataset.Dispose()
raise Exception("Frames are not equal.")
image_frame = dataset.GetFrame(0, frames - 1)
image_array = np.frombuffer(dataset.GetDataBuffer(), dtype = 'uint16').reshape((image_frame.Width, frames), order = 'F')
return image_array
def lightField_closing(self, sender, event_args):
#Close the shutter for security
self._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysClosed')
self.unhook_events()
self.close()
def open_shutter(self, openShutter = True):
if openShutter:
self._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysOpen')
else:
self._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysClosed')
def setNframes(self, Nframes = 1000):
self._experiment.SetValue(ExperimentSettings.AcquisitionFramesToStore, str(Nframes))
def unhook_events(self):
# Unhook the eventhandler for IsReadyToRunChanged
# Will be called upon exiting
self._auto.LightFieldClosing -= self.lightField_closing
def stop(self):
pass
def close(self):
self._auto.Dispose()
def get_x_axis(self):
return np.arange(1024)
if __name__ == "__main__":
pilf = PILF()
pilf._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysOpen')
image_array = pilf.capture_spectra()
pilf._experiment.SetValue(CameraSettings.ShutterTimingMode, 'AlwaysClosed')
pilf.lightField_closing(1,1)

View file

@ -0,0 +1,90 @@
from pymodaq.extensions.pid.utils import PIDModelGeneric, OutputToActuator, InputFromDetector, main
from pymodaq.utils.data import DataToExport
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 InputFromDetector([y, x])
def convert_output(self, outputs: List[float], dt: float, stab=True):
"""
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 OutputToActuator(mode='rel', values=outputs)
if __name__ == '__main__':
main("BeamSteeringMockNoModel.xml") # some preset configured with the right actuators and detectors

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Created the 01/06/2023
@author: Sebastien Weber
"""

View file

@ -0,0 +1 @@
0.0.1

View file

@ -0,0 +1,2 @@
#this is the configuration file of the plugin

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Created the 01/06/2023
@author: Sebastien Weber
"""

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
"""
Created the 31/08/2023
@author: Sebastien Weber
"""
from pathlib import Path
from pymodaq.utils.config import BaseConfig, USER
class Config(BaseConfig):
"""Main class to deal with configuration values for this plugin"""
config_template_path = Path(__file__).parent.joinpath('resources/config_template.toml')
config_name = f"config_{__package__.split('pymodaq_plugins_')[1]}"

View file

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""
Created the 17/10/2023
@author: Sebastien Weber
"""
import pytest
from pathlib import Path
import importlib
import pkgutil
MANDATORY_MOVE_METHODS = ['ini_attributes', 'get_actuator_value', 'close', 'commit_settings',
'ini_stage', 'move_abs', 'move_home', 'move_rel', 'stop_motion']
MANDATORY_VIEWER_METHODS = ['ini_attributes', 'grab_data', 'close', 'commit_settings',
'ini_detector', ]
def get_package_name():
here = Path(__file__).parent
package_name = here.parent.stem
return package_name
def get_move_plugins():
pkg_name = get_package_name()
move_mod = importlib.import_module(f'{pkg_name}.daq_move_plugins')
plugin_list = [mod for mod in [mod[1] for mod in
pkgutil.iter_modules([str(move_mod.path.parent)])]
if 'daq_move_' in mod]
return plugin_list, move_mod
def get_viewer_plugins(dim='0D'):
pkg_name = get_package_name()
viewer_mod = importlib.import_module(f'{pkg_name}.daq_viewer_plugins.plugins_{dim}')
plugin_list = [mod for mod in [mod[1] for mod in
pkgutil.iter_modules([str(viewer_mod.path.parent)])]
if f'daq_{dim}viewer_' in mod]
return plugin_list, viewer_mod
def test_package_name_ok():
assert 'pymodaq_plugins_' in get_package_name()[0:16]
def test_imports():
pkg_name = get_package_name()
mod = importlib.import_module(pkg_name)
assert hasattr(mod, 'config')
assert hasattr(mod, '__version__')
move_mod = importlib.import_module(f'{pkg_name}', 'daq_move_plugins')
importlib.import_module(f'{pkg_name}', 'daq_viewer_plugins')
importlib.import_module(f'{pkg_name}', 'extensions')
importlib.import_module(f'{pkg_name}', 'models')
importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_0D')
importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_1D')
importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_2D')
importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_ND')
def test_move_inst_plugins_name():
plugin_list, move_mod = get_move_plugins()
for plug in plugin_list:
name = plug.split('daq_move_')[1]
assert hasattr(getattr(move_mod, plug), f'DAQ_Move_{name}')
def test_move_has_mandatory_methods():
plugin_list, move_mod = get_move_plugins()
for plug in plugin_list:
name = plug.split('daq_move_')[1]
klass = getattr(getattr(move_mod, plug), f'DAQ_Move_{name}')
for meth in MANDATORY_MOVE_METHODS:
assert hasattr(klass, meth)
@pytest.mark.parametrize('dim', ('0D', '1D', '2D', 'ND'))
def test_viewer_has_mandatory_methods(dim):
plugin_list, mod = get_viewer_plugins(dim)
for plug in plugin_list:
name = plug.split(f'daq_{dim}viewer_')[1]
try:
module = importlib.import_module(f'.{plug}', mod.__package__)
except Exception:
break
klass = getattr(module, f'DAQ_{dim}Viewer_{name}')
for meth in MANDATORY_VIEWER_METHODS:
assert hasattr(klass, meth)

3
tox.ini Normal file
View file

@ -0,0 +1,3 @@
[flake8]
exclude = .git,__pycache__,build,dist,pymodaq/QtDesigner_Ressources
ignore = E501, F401, F841, F811, F403