diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml new file mode 100644 index 0000000..ffd1522 --- /dev/null +++ b/.github/workflows/Test.yml @@ -0,0 +1,10 @@ +name: 'Tests' + +on: [push] + +jobs: + call_workflow: + uses: ./.github/workflows/Testbase.yml + with: + python: '3.11' + qt5: 'pyqt5' \ No newline at end of file diff --git a/.github/workflows/Testbase.yml b/.github/workflows/Testbase.yml new file mode 100644 index 0000000..989a2a0 --- /dev/null +++ b/.github/workflows/Testbase.yml @@ -0,0 +1,44 @@ +name: Base + +on: + workflow_call: + inputs: + python: + required: true + type: string + qt5: + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + env: + DISPLAY: ':99.0' + QT_DEBUG_PLUGINS: 1 + steps: + - name: Set up Python ${{ inputs.python }} + uses: actions/checkout@v4 + - name: Install dependencies + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python }} + - name: Install package + run: | + sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils + python -m pip install --upgrade pip + export QT_DEBUG_PLUGINS=1 + pip install flake8 pytest pytest-cov pytest-qt pytest-xdist pytest-xvfb setuptools wheel numpy h5py ${{ inputs.qt5 }} toml + pip install pymodaq pyqt5 + pip install -e . + - name: create local pymodaq folder and setting permissions + run: | + sudo mkdir /etc/.pymodaq + sudo chmod uo+rw /etc/.pymodaq + - name: Linting with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=src/pymodaq/resources/QtDesigner_Ressources,docs + - name: Test with pytest + run: | + pytest -n auto diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index cc1ffb5..69806c5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,19 +13,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine toml - - name: Build and publish + pip install hatch hatchling toml twine + - name: Get history and tags for SCM versioning to work + run: | + git branch + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + hatch version + - name: Build + run: hatch build + - name: Check the build + run: twine check dist/* + - name: publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }} + HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel - twine upload dist/* + hatch publish diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml new file mode 100644 index 0000000..a80cc91 --- /dev/null +++ b/.github/workflows/updater.yml @@ -0,0 +1,23 @@ +name: GitHub Actions Version Updater + +# Controls when the action will run. +on: + schedule: + # Automatically run at 00:00 on day-of-month 5. + - cron: '0 0 5 * *' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + # [Required] Access token with `workflow` scope. + token: ${{ secrets.WORKFLOW_SECRET }} + + - name: Run GitHub Actions Version Updater + uses: saadmk11/github-actions-version-updater@v0.8.1 + with: + # [Required] Access token with `workflow` scope. + token: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 0000000..a0208ee --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,10 @@ +from pathlib import Path +from hatchling.metadata.plugin.interface import MetadataHookInterface +from pymodaq_utils.resources.hatch_build_plugins import update_metadata_from_toml + +here = Path(__file__).absolute().parent + + +class PluginInfoTomlHook(MetadataHookInterface): + def update(self, metadata: dict) -> None: + update_metadata_from_toml(metadata, here) diff --git a/plugin_info.toml b/plugin_info.toml deleted file mode 100644 index 8b346c5..0000000 --- a/plugin_info.toml +++ /dev/null @@ -1,23 +0,0 @@ -## To modify by developper(s) of the plugin - -[plugin-info] -SHORT_PLUGIN_NAME = 'opencv' - -package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_opencv' #to modify -description = 'plugin to use camera instrumented using the opencv library' - -author = 'Sebastien J. Weber' -author-email = 'sebastien.weber@cnrs.fr' -license = 'MIT' - -[plugin-install] -#packages required for your plugin: -packages-required = ['pymodaq>=4.0.0', 'opencv-python'] - -[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 -pid_models = false # true if plugins contains pid models -h5exporters = false # true if plugin contains custom h5 file exporters -scanners = false # true if plugin contains custom scan layout (daq_scan extensions) - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..32db196 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[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 +h5exporters = false # true if plugin contains custom h5 file exporters +scanners = false # true if plugin contains custom scan layout (daq_scan extensions) + +[urls] +package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_opencv' + +[project] +name = "pymodaq_plugins_opencv" +description = 'plugin to use camera instrumented using the opencv library' +dependencies = [ + "pymodaq>=5.0.0", + 'opencv-python', +] + +authors = [ + {name = "Sebastien J. Weber", email = "'sebastien.weber@cnrs.fr'"}, +] +maintainers = [ + {name = "Sebastien J. Weber", email = "'sebastien.weber@cnrs.fr'"}, +] + +# nottodo: leave everything below as is! + +dynamic = ["version", "urls", "entry-points"] +readme = "README.rst" +license = { file="LICENSE" } +requires-python = ">=3.8" + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Human Machine Interfaces", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: User Interfaces", +] + +[build-system] +requires = [ + "hatchling>=1.9.0", + "hatch-vcs", "toml", + "pymodaq_utils>=0.0.6", +] +build-backend = "hatchling.build" + +[tool.hatch.metadata.hooks.custom] + +[tool.hatch.version] +source = "vcs" + diff --git a/setup.py b/setup.py deleted file mode 100644 index b58df91..0000000 --- a/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -from setuptools import setup, find_packages -import toml - -config = toml.load('./plugin_info.toml') -SHORT_PLUGIN_NAME = config['plugin-info']['SHORT_PLUGIN_NAME'] -PLUGIN_NAME = f"pymodaq_plugins_{SHORT_PLUGIN_NAME}" - - -from pathlib import Path - -if not SHORT_PLUGIN_NAME.isidentifier(): - raise ValueError("'SHORT_PLUGIN_NAME = %s' is not a valid python identifier." % SHORT_PLUGIN_NAME) - -version_file = Path(__file__).parent.joinpath(f'src/{PLUGIN_NAME}/resources/VERSION') # new location of the version file -if not version_file.is_file(): - version_file = Path(__file__).parent.joinpath(f'src/{PLUGIN_NAME}/VERSION') - -with open(str(version_file), 'r') as fvers: - version = fvers.read().strip() - - -with open('README.rst') as fd: - long_description = fd.read() - -setupOpts = dict( - name=PLUGIN_NAME, - description=config['plugin-info']['description'], - long_description=long_description, - license=config['plugin-info']['license'], - url=config['plugin-info']['package-url'], - author=config['plugin-info']['author'], - author_email=config['plugin-info']['author-email'], - classifiers=[ - "Programming Language :: Python :: 3", - "Development Status :: 5 - Production/Stable", - "Environment :: Other Environment", - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering :: Human Machine Interfaces", - "Topic :: Scientific/Engineering :: Visualization", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Software Development :: User Interfaces", - ], ) - -entrypoints = {} -if 'features' in config: - if config['features'].get('instruments', False): - entrypoints['pymodaq.instruments'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('extensions', False): - entrypoints['pymodaq.extensions'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('pid_models', False): - entrypoints['pymodaq.pid_models'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('h5exporters', False): - entrypoints['pymodaq.h5exporters'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('scans', False): - entrypoints['pymodaq.scanners'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' -else: - entrypoints['pymodaq.scanners'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - -setup( - version=version, - packages=find_packages(where='./src'), - package_dir={'': 'src'}, - include_package_data=True, - entry_points=entrypoints, - install_requires=['toml', ]+config['plugin-install']['packages-required'], - **setupOpts -) \ No newline at end of file diff --git a/src/pymodaq_plugins_opencv/__init__.py b/src/pymodaq_plugins_opencv/__init__.py index ca051e5..9b1abfe 100644 --- a/src/pymodaq_plugins_opencv/__init__.py +++ b/src/pymodaq_plugins_opencv/__init__.py @@ -1,8 +1,13 @@ from pathlib import Path -from pymodaq.utils.logger import set_logger # to be imported by other modules. - from .utils import Config +from pymodaq_utils.utils import get_version, PackageNotFoundError +from pymodaq_utils.logger import set_logger, get_module_name + config = Config() +try: + __version__ = get_version(__package__) +except PackageNotFoundError: + __version__ = '0.0.0dev' + + -with open(str(Path(__file__).parent.joinpath('resources/VERSION')), 'r') as fvers: - __version__ = fvers.read().strip() diff --git a/src/pymodaq_plugins_opencv/daq_viewer_plugins/plugins_2D/daq_2Dviewer_OpenCV.py b/src/pymodaq_plugins_opencv/daq_viewer_plugins/plugins_2D/daq_2Dviewer_OpenCV.py index 636519c..0eb2d21 100644 --- a/src/pymodaq_plugins_opencv/daq_viewer_plugins/plugins_2D/daq_2Dviewer_OpenCV.py +++ b/src/pymodaq_plugins_opencv/daq_viewer_plugins/plugins_2D/daq_2Dviewer_OpenCV.py @@ -3,10 +3,10 @@ import numpy as np import cv2 -from pymodaq.utils.daq_utils import ThreadCommand +from pymodaq_utils.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 +from pymodaq_gui.parameter import Parameter from pymodaq_plugins_opencv.hardware.opencv import OpenCVProp diff --git a/src/pymodaq_plugins_opencv/extensions/myextension.py b/src/pymodaq_plugins_opencv/extensions/myextension.py deleted file mode 100644 index 1f458ad..0000000 --- a/src/pymodaq_plugins_opencv/extensions/myextension.py +++ /dev/null @@ -1,195 +0,0 @@ -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() - - diff --git a/src/pymodaq_plugins_opencv/hardware/opencv.py b/src/pymodaq_plugins_opencv/hardware/opencv.py index 272cf26..2321e75 100644 --- a/src/pymodaq_plugins_opencv/hardware/opencv.py +++ b/src/pymodaq_plugins_opencv/hardware/opencv.py @@ -1,4 +1,4 @@ -from pymodaq.utils.enums import BaseEnum, enum_checker +from pymodaq_utils.enums import BaseEnum, enum_checker import cv2 diff --git a/src/pymodaq_plugins_opencv/models/PIDModelTemplate.py b/src/pymodaq_plugins_opencv/models/PIDModelTemplate.py deleted file mode 100644 index 2f47f13..0000000 --- a/src/pymodaq_plugins_opencv/models/PIDModelTemplate.py +++ /dev/null @@ -1,90 +0,0 @@ -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 - - diff --git a/src/pymodaq_plugins_opencv/resources/VERSION b/src/pymodaq_plugins_opencv/resources/VERSION deleted file mode 100644 index 7bcd0e3..0000000 --- a/src/pymodaq_plugins_opencv/resources/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.0.2 \ No newline at end of file diff --git a/src/pymodaq_plugins_opencv/utils.py b/src/pymodaq_plugins_opencv/utils.py index 1f225ab..4981b95 100644 --- a/src/pymodaq_plugins_opencv/utils.py +++ b/src/pymodaq_plugins_opencv/utils.py @@ -6,7 +6,7 @@ """ from pathlib import Path -from pymodaq.utils.config import BaseConfig, USER +from pymodaq_utils.config import BaseConfig, USER class Config(BaseConfig): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 0d50c11..0000000 --- a/tox.ini +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -exclude = .git,__pycache__,build,dist,pymodaq/QtDesigner_Ressources -ignore = E501, F401, F841, F811, F403