25032024 lightfield pymodaq plugin
This commit is contained in:
parent
fd977cd831
commit
49bd905d9b
30 changed files with 1296 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal 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
3
MANIFEST.in
Normal file
|
@ -0,0 +1,3 @@
|
|||
include README.rst
|
||||
include LICENSE
|
||||
graft src
|
BIN
icon.ico
Normal file
BIN
icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
24
plugin_info.toml
Normal file
24
plugin_info.toml
Normal 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
4
setup.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from pymodaq.resources.setup_plugin import setup
|
||||
from pathlib import Path
|
||||
|
||||
setup(Path(__file__).parent)
|
8
src/pymodaq_plugins_lightfield/__init__.py
Normal file
8
src/pymodaq_plugins_lightfield/__init__.py
Normal 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()
|
13
src/pymodaq_plugins_lightfield/daq_move_plugins/__init__.py
Normal file
13
src/pymodaq_plugins_lightfield/daq_move_plugins/__init__.py
Normal 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
|
||||
|
|
@ -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 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
|
||||
|
||||
"""
|
||||
_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__)
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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 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: 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__)
|
|
@ -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
|
||||
|
|
@ -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 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+[{'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__)
|
|
@ -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
|
||||
|
||||
|
|
@ -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 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: 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__)
|
|
@ -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
|
||||
|
6
src/pymodaq_plugins_lightfield/exporters/__init__.py
Normal file
6
src/pymodaq_plugins_lightfield/exporters/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created the 01/06/2023
|
||||
|
||||
@author: Sebastien Weber
|
||||
"""
|
6
src/pymodaq_plugins_lightfield/extensions/__init__.py
Normal file
6
src/pymodaq_plugins_lightfield/extensions/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created the 01/06/2023
|
||||
|
||||
@author: Sebastien Weber
|
||||
"""
|
195
src/pymodaq_plugins_lightfield/extensions/myextension.py
Normal file
195
src/pymodaq_plugins_lightfield/extensions/myextension.py
Normal 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()
|
||||
|
||||
|
0
src/pymodaq_plugins_lightfield/hardware/__init__.py
Normal file
0
src/pymodaq_plugins_lightfield/hardware/__init__.py
Normal file
115
src/pymodaq_plugins_lightfield/hardware/my_lightfield_library.py
Normal file
115
src/pymodaq_plugins_lightfield/hardware/my_lightfield_library.py
Normal 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)
|
90
src/pymodaq_plugins_lightfield/models/PIDModelTemplate.py
Normal file
90
src/pymodaq_plugins_lightfield/models/PIDModelTemplate.py
Normal 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
|
||||
|
||||
|
6
src/pymodaq_plugins_lightfield/models/__init__.py
Normal file
6
src/pymodaq_plugins_lightfield/models/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created the 01/06/2023
|
||||
|
||||
@author: Sebastien Weber
|
||||
"""
|
1
src/pymodaq_plugins_lightfield/resources/VERSION
Normal file
1
src/pymodaq_plugins_lightfield/resources/VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.0.1
|
0
src/pymodaq_plugins_lightfield/resources/__init__.py
Normal file
0
src/pymodaq_plugins_lightfield/resources/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
#this is the configuration file of the plugin
|
||||
|
6
src/pymodaq_plugins_lightfield/scanners/__init__.py
Normal file
6
src/pymodaq_plugins_lightfield/scanners/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created the 01/06/2023
|
||||
|
||||
@author: Sebastien Weber
|
||||
"""
|
15
src/pymodaq_plugins_lightfield/utils.py
Normal file
15
src/pymodaq_plugins_lightfield/utils.py
Normal 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]}"
|
91
tests/test_plugin_package_structure.py
Normal file
91
tests/test_plugin_package_structure.py
Normal 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
3
tox.ini
Normal file
|
@ -0,0 +1,3 @@
|
|||
[flake8]
|
||||
exclude = .git,__pycache__,build,dist,pymodaq/QtDesigner_Ressources
|
||||
ignore = E501, F401, F841, F811, F403
|
Loading…
Reference in a new issue