pymodaq_plugins_oceaninsight 5.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymodaq_plugins_oceaninsight-5.3.0/.gitattributes +2 -0
- pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/compatibility.yml +15 -0
- pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/python-publish.yml +11 -0
- pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/tests.yml +8 -0
- pymodaq_plugins_oceaninsight-5.3.0/.gitignore +116 -0
- pymodaq_plugins_oceaninsight-5.3.0/AGENTS.md +206 -0
- pymodaq_plugins_oceaninsight-5.3.0/CLAUDE.md +3 -0
- pymodaq_plugins_oceaninsight-5.3.0/LICENSE +21 -0
- pymodaq_plugins_oceaninsight-5.3.0/PKG-INFO +87 -0
- pymodaq_plugins_oceaninsight-5.3.0/README.rst +34 -0
- pymodaq_plugins_oceaninsight-5.3.0/hatch_build.py +10 -0
- pymodaq_plugins_oceaninsight-5.3.0/icon.ico +0 -0
- pymodaq_plugins_oceaninsight-5.3.0/pyproject.toml +65 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/__init__.py +12 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_move_plugins/__init__.py +13 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/__init__.py +3 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_0D/__init__.py +13 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/__init__.py +13 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/daq_1Dviewer_Omnidriver.py +177 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/daq_1Dviewer_Seabreeze.py +164 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_2D/__init__.py +14 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_ND/__init__.py +13 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/hardware/__init__.py +0 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/resources/__init__.py +0 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/resources/config_template.toml +3 -0
- pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/utils.py +15 -0
- pymodaq_plugins_oceaninsight-5.3.0/tests/test_plugin_package_structure.py +28 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
|
|
2
|
+
name: Compatibility with pymodaq (latest release)
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main, master, '[0-9]*.x']
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
compatibility:
|
|
15
|
+
uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-compatibility.yml@5.3.x
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
|
|
2
|
+
name: Upload Python Package
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
release:
|
|
6
|
+
types: [created]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-publish.yml@5.3.x
|
|
11
|
+
secrets: inherit
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
|
|
2
|
+
name: Tests
|
|
3
|
+
|
|
4
|
+
on: [push, pull_request]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
tests:
|
|
8
|
+
uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-tests.yml@5.3.x
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Compiled python modules.
|
|
2
|
+
*.pyc
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
.Python
|
|
15
|
+
build/
|
|
16
|
+
develop-eggs/
|
|
17
|
+
dist/
|
|
18
|
+
downloads/
|
|
19
|
+
eggs/
|
|
20
|
+
.eggs/
|
|
21
|
+
lib/
|
|
22
|
+
lib64/
|
|
23
|
+
parts/
|
|
24
|
+
sdist/
|
|
25
|
+
var/
|
|
26
|
+
wheels/
|
|
27
|
+
*.egg-info/
|
|
28
|
+
.installed.cfg
|
|
29
|
+
*.egg
|
|
30
|
+
MANIFEST
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
*.manifest
|
|
36
|
+
*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
pip-log.txt
|
|
40
|
+
pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
htmlcov/
|
|
44
|
+
.tox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
|
|
63
|
+
# Flask stuff:
|
|
64
|
+
instance/
|
|
65
|
+
.webassets-cache
|
|
66
|
+
|
|
67
|
+
# Scrapy stuff:
|
|
68
|
+
.scrapy
|
|
69
|
+
|
|
70
|
+
# Sphinx documentation
|
|
71
|
+
docs/_build/
|
|
72
|
+
|
|
73
|
+
# PyBuilder
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# pyenv
|
|
80
|
+
.python-version
|
|
81
|
+
|
|
82
|
+
# celery beat schedule file
|
|
83
|
+
celerybeat-schedule
|
|
84
|
+
|
|
85
|
+
# SageMath parsed files
|
|
86
|
+
*.sage.py
|
|
87
|
+
|
|
88
|
+
# Environments
|
|
89
|
+
.env
|
|
90
|
+
.venv
|
|
91
|
+
env/
|
|
92
|
+
venv/
|
|
93
|
+
ENV/
|
|
94
|
+
env.bak/
|
|
95
|
+
venv.bak/
|
|
96
|
+
|
|
97
|
+
# Spyder project settings
|
|
98
|
+
.spyderproject
|
|
99
|
+
.spyproject
|
|
100
|
+
|
|
101
|
+
# Rope project settings
|
|
102
|
+
.ropeproject
|
|
103
|
+
|
|
104
|
+
# mkdocs documentation
|
|
105
|
+
/site
|
|
106
|
+
|
|
107
|
+
# mypy
|
|
108
|
+
.mypy_cache/
|
|
109
|
+
|
|
110
|
+
# pycharm
|
|
111
|
+
.idea/
|
|
112
|
+
.idea/*
|
|
113
|
+
|
|
114
|
+
*yacctab.py
|
|
115
|
+
*lextab.py
|
|
116
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding assistants (and humans) building on **PyMoDAQ 5.3.x** from this template.
|
|
4
|
+
PyMoDAQ is a Qt framework that drives lab experiments: hardware is added through small *plugins*, and PyMoDAQ
|
|
5
|
+
provides the GUI, threading, data model, scans and HDF5 saving. Python 3.10 to 3.13.
|
|
6
|
+
|
|
7
|
+
This repository is a plugin package named `pymodaq_plugins_<name>`. Rename everything that still says `template`
|
|
8
|
+
before publishing.
|
|
9
|
+
|
|
10
|
+
## 1. Choose what to build
|
|
11
|
+
|
|
12
|
+
Prefer the first option that fits:
|
|
13
|
+
|
|
14
|
+
1. **Instrument plugin** (`daq_move_plugins/`, `daq_viewer_plugins/`): a new device must appear in the Dashboard.
|
|
15
|
+
2. **Dashboard extension** (`extensions/`, class derived from `CustomExt`): a workflow that uses instruments
|
|
16
|
+
(scans, feedback, custom panels, logging). It runs inside the Dashboard and reuses its stable mechanisms for
|
|
17
|
+
instruments: the experiment manager and the state manager (how a Dashboard setup is described, loaded and
|
|
18
|
+
restored; the word "preset" is obsolete), the module manager, control-module threading, shared controllers,
|
|
19
|
+
saving. **If the application drives instruments, build a `CustomExt`, not a `CustomApp`.**
|
|
20
|
+
3. **Standalone app** (`app/`, class derived from `CustomApp`): only when the application needs **no
|
|
21
|
+
instruments** (data viewer, analysis, h5 browsing, configuration tools).
|
|
22
|
+
4. A plain script, for a one-off sequence that does not need a GUI.
|
|
23
|
+
|
|
24
|
+
A **model** (section 6) is not a separate option: it tailors an existing extension (PID, optimizers, data mixer,
|
|
25
|
+
sequencer) to your experiment.
|
|
26
|
+
|
|
27
|
+
## 2. Workflow
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install -e . # editable install of this plugin, from the repository root
|
|
31
|
+
check_plugin # static report on this package (hardware not needed)
|
|
32
|
+
check_plugin --fail-on todo -v # also list every unfinished TODO
|
|
33
|
+
pytest # template tests (package structure, plugin checks, behaviour against a fake controller)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Installing PyMoDAQ itself is a separate matter. A plugin only needs the released packages (`pymodaq`,
|
|
37
|
+
`pymodaq_utils`, `pymodaq_data`, `pymodaq_gui`, installed as dependencies). If you also have to modify them, work in a
|
|
38
|
+
clone of the PyMoDAQ repository and install the four packages in editable mode with the script at its root, never with
|
|
39
|
+
`pip install -e .` inside each package (the script handles the order and the options):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python install-packages.py -d # editable + dev extras, from the PyMoDAQ repository root
|
|
43
|
+
python install-packages.py -d -qt pyside6 # choose the Qt backend (pyqt5, pyqt6 or pyside6)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Do not consider a task finished until `check_plugin` and `pytest` pass. Do not invent PyMoDAQ APIs: if a method
|
|
47
|
+
or attribute is not in the base classes (`DAQ_Move_base`, `DAQ_Viewer_base`, `CustomExt`) or in an existing
|
|
48
|
+
plugin, look it up in the installed `pymodaq` package instead of guessing.
|
|
49
|
+
|
|
50
|
+
## 3. Instrument plugins
|
|
51
|
+
|
|
52
|
+
File and class names must match: `daq_move_<Name>.py` holds `DAQ_Move_<Name>`; `daq_<N>Dviewer_<Name>.py` holds
|
|
53
|
+
`DAQ_<N>DViewer_<Name>` (N = 0, 1, 2 or N). Each file ends with `if __name__ == '__main__': main(__file__)`.
|
|
54
|
+
|
|
55
|
+
**Actuator** (`DAQ_Move_base`). Override at least, in the order of the template (the order in which to develop them),
|
|
56
|
+
`ini_attributes`, `ini_stage`, `close`, `get_actuator_value`, `move_abs`, `move_rel`, `move_home`, `stop_motion`,
|
|
57
|
+
`commit_settings`.
|
|
58
|
+
|
|
59
|
+
- `ini_stage(controller=None)` returns `(info: str, initialized: bool)`. If `self.is_master`, create the controller;
|
|
60
|
+
otherwise store the `controller` passed in (several axes share one controller).
|
|
61
|
+
- Class attributes: `is_multiaxes`, `_axis_names`, `_controller_units`, `_epsilons`,
|
|
62
|
+
`data_actuator_type = DataActuatorType.DataActuator`, optionally `ui_type` and `has_encoder`. Always write the
|
|
63
|
+
`data_actuator_type` line: the default of the base class is `DataActuatorType.float`, a backcompatibility mode where
|
|
64
|
+
`move_abs`/`move_rel` receive a plain float (already in the axis unit) and `get_actuator_value` returns one. PyMoDAQ
|
|
65
|
+
guards that case, but do not use it in new code.
|
|
66
|
+
- `params` = list of dicts + `comon_parameters_fun(is_multiaxes, axis_names=..., epsilon=...)`.
|
|
67
|
+
- Positions are `DataActuator` objects with **units** (`units=self.axis_unit`); use `check_bound`,
|
|
68
|
+
`set_position_with_scaling` and `get_position_with_scaling` as in the template.
|
|
69
|
+
|
|
70
|
+
**Detector** (`DAQ_Viewer_base`). Override at least, in the order of the template, `ini_attributes`, `ini_detector`,
|
|
71
|
+
`close`, `grab_data`, `stop`, `commit_settings`.
|
|
72
|
+
|
|
73
|
+
- `ini_detector(controller=None)` returns `(info, initialized)`, same master/slave logic.
|
|
74
|
+
- `params = comon_parameters + [...]`.
|
|
75
|
+
- Data leaves the plugin as `DataToExport` containing `DataFromPlugins` (with `dim='Data0D'|'Data1D'|'Data2D'|'DataND'`,
|
|
76
|
+
`labels`, `axes=[Axis(...)]`) emitted through `self.dte_signal` (grab) or `self.dte_signal_temp` (live preview).
|
|
77
|
+
|
|
78
|
+
**Rules that matter**
|
|
79
|
+
|
|
80
|
+
- Plugin code runs in a worker thread. **Never create or touch Qt widgets** from it. Report progress with
|
|
81
|
+
`self.emit_status(ThreadCommand('Update_Status', ['message']))`.
|
|
82
|
+
- Do not block for long inside `grab_data` or a move; use the asynchronous pattern shown in the template and the
|
|
83
|
+
polling that PyMoDAQ already does (`epsilon`).
|
|
84
|
+
- Always release the hardware in `close()`, only if `self.is_master and self.controller is not None` (the init may have
|
|
85
|
+
failed before the controller existed). Leave actuators in a safe state on error.
|
|
86
|
+
- Settings are `pyqtgraph` Parameter dicts (`{'title', 'name', 'type', 'value', ...}`). React to changes in
|
|
87
|
+
`commit_settings(param)` by `param.name()`.
|
|
88
|
+
- Units are `pint`-based. Give every actuator value and every `Axis` a unit; mismatches raise `DataUnitError`.
|
|
89
|
+
What you send to a controller must be expressed in the controller units: use `value.value(self.axis_unit)` in
|
|
90
|
+
`move_abs` / `move_rel`, never `value.value()` (the magnitude in whatever unit the `DataActuator` carries, so 1 cm
|
|
91
|
+
would be sent as 1.0 to a controller in mm). `axis_unit` is the unit of the current axis, `axis_units` the list/dict
|
|
92
|
+
of all the axes of a multiaxes controller.
|
|
93
|
+
- Hardware access code goes in `hardware/`, behind a small wrapper class. This lets the plugin be tested with a
|
|
94
|
+
mock controller and keeps the plugin file readable.
|
|
95
|
+
- Defaults live in `resources/config_template.toml`, not in the code. Read configuration values through
|
|
96
|
+
`GlobalConfig` from `pymodaq_utils.config`: a singleton that wraps the `Config` objects of all packages, e.g.
|
|
97
|
+
`GlobalConfig()('gui', 'style', 'theme')`. Do not parse the toml files yourself, and do not instantiate a `Config`
|
|
98
|
+
class directly (deprecated): PyMoDAQ registers the `Config` of `<your_package>/utils.py` in `GlobalConfig` when it
|
|
99
|
+
discovers the plugin, under the package name without `pymodaq_plugins_`.
|
|
100
|
+
|
|
101
|
+
**Testing behaviour without hardware.** `pymodaq.utils.plugin_testing` (shipped with PyMoDAQ >= 5.3.2) drives a plugin the way PyMoDAQ does, without a GUI:
|
|
102
|
+
`make_actuator` / `make_detector` (instantiate and call `ini_stage` / `ini_detector`), `move_abs_and_wait`,
|
|
103
|
+
`move_rel_and_wait`, `move_home_and_wait`, `grab_and_wait` (return the final position or the `DataToExport`, and fail
|
|
104
|
+
on a timeout) and `assert_units`. `tests/example_mock_plugins.py` shows a fake controller and the smallest actuator,
|
|
105
|
+
multi-axes actuator and 1D detector; `tests/test_plugin_behaviour.py` shows the tests to copy. Write the vendor
|
|
106
|
+
communication behind a wrapper class in `hardware/`, write a fake with the same public methods, and run your real
|
|
107
|
+
plugin class against it (monkeypatch the wrapper class). Targets reach the plugin in the axis unit, as in PyMoDAQ.
|
|
108
|
+
|
|
109
|
+
These tests only check your side of the PyMoDAQ contract (return types, units, signals, shared controller, `close`)
|
|
110
|
+
against a fake that encodes what you believe the driver does. They say nothing about the device: timing, error replies,
|
|
111
|
+
real ranges and units. A passing run is not a hardware validation: never state that a plugin works on an instrument
|
|
112
|
+
that was not used, say what was tested on the real device and what only against a fake.
|
|
113
|
+
|
|
114
|
+
**Legacy patterns to avoid** (older tutorials and models still produce them; `check_plugin` flags several):
|
|
115
|
+
`stage_names` (use `_axis_names`), `_epsilon` (use `_epsilons`), `data_actuator_type = float` (use
|
|
116
|
+
`DataActuatorType.DataActuator`), the group names `multiaxes` and `multi_status` (now `controller` and
|
|
117
|
+
`controller_status`), importing `CustomExt` from `pymodaq.extensions.custom_ext` (use `pymodaq.utils.custom_ext`),
|
|
118
|
+
and the deprecated `CustomApp` / `CustomExt` hooks `setup_docks` and `setup_menu` (use `setup_docks_and_widgets` and
|
|
119
|
+
`setup_menus_and_toolbars`).
|
|
120
|
+
|
|
121
|
+
## 4. Dashboard extensions (`CustomExt`)
|
|
122
|
+
|
|
123
|
+
- Entry point group `pymodaq.extensions` in `pyproject.toml`; each module in `extensions/` defines
|
|
124
|
+
`EXTENSION_NAME` (menu label), `CLASS_NAME` (class name) and a class derived from `CustomExt`.
|
|
125
|
+
- Import: `from pymodaq.utils.custom_ext import CustomExt`.
|
|
126
|
+
- A plugin extension follows exactly the pattern of the core extensions (`daq_scan`, `sequencer`, `ramping`, data
|
|
127
|
+
mixer). The only difference is `EXTENSION_NAME` and `CLASS_NAME`, which the Dashboard needs to recognise it.
|
|
128
|
+
- **`_quit_fun(self) -> bool` must be overridden and return `True`** (return `False`, after telling the user, while
|
|
129
|
+
the extension is running). The base implementation returns `None`, which prevents closing.
|
|
130
|
+
- **`main()` pattern** (to run the extension alone, same as the core ones):
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
def main():
|
|
134
|
+
import sys
|
|
135
|
+
from pymodaq_gui.qt_utils import mkQApp
|
|
136
|
+
from pymodaq.dashboard import load_dashboard_with_arguments
|
|
137
|
+
from pymodaq.utils.gui_utils.loader_utils import create_extension
|
|
138
|
+
|
|
139
|
+
app = mkQApp('My Extension')
|
|
140
|
+
win, dashboard, _ = load_dashboard_with_arguments(show_dashboard=False, load_extension=False)
|
|
141
|
+
win.mainwindow.setVisible(False)
|
|
142
|
+
win_ext, ext = create_extension(dashboard, MyExtension, show_extension=True)
|
|
143
|
+
sys.exit(app.exec())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`load_dashboard_with_arguments` reads the command line (`-x EXPERIMENT_NAME`, `-s STATE_NAME`).
|
|
147
|
+
`create_extension` already shows the window: do not call `win_ext.show()` again, and do not use `create_load_dashboard`
|
|
148
|
+
here.
|
|
149
|
+
- Long-running work (a scan, a sequence, a ramp) goes in an `ExtensionWorker` subclass run in a thread, started and
|
|
150
|
+
stopped from the extension's workflow actions, as in `sequencer.py` and `ramping.py`.
|
|
151
|
+
- `__init__(self, parent: DockArea, dashboard)`: call `super().__init__(parent, dashboard)`, then `self.setup_ui()`.
|
|
152
|
+
- **Experiment and state belong to the Dashboard.** The *experiment* is the list of instruments (actuators and
|
|
153
|
+
detectors) the Dashboard manages and controls; the *state* is the state of that experiment, mostly the values of the
|
|
154
|
+
instruments' settings and of the Dashboard's actuators. An extension must use the managers it gets through its
|
|
155
|
+
`dashboard` argument, as `self.experiment_manager` and `self.state_manager`, and never create its own, nor its own
|
|
156
|
+
save/load of instrument configurations. Override `do_things_after_experiment_set(experiment_name, show_dashboard)`
|
|
157
|
+
to react when an experiment is set (the base class refreshes `self.modules_manager` there), and use
|
|
158
|
+
`create_dashboard_toolbar(...)` to expose the experiment and state actions in your extension.
|
|
159
|
+
- The Dashboard's instruments are reached through `self.modules_manager`. This `ModulesManager` is created anew for
|
|
160
|
+
each extension by the base class `__init__`, so that every extension can handle different instruments: never share
|
|
161
|
+
it between extensions, replace it, or reuse the Dashboard's one. Do not create control modules yourself inside an
|
|
162
|
+
extension, and do not talk to the hardware directly.
|
|
163
|
+
- Implement the lifecycle, in the order `setup_ui` calls it: `setup_docks_and_widgets`, `setup_menus_and_toolbars`
|
|
164
|
+
(only creates the menus and toolbars), `setup_actions` (creates the actions and can put them in those menus and
|
|
165
|
+
toolbars with `add_action(..., menu=..., toolbar=...)`), `connect_things`, then `value_changed`, `quit_fun`. `setup_docks` and `setup_menu` are deprecated. The GUI thread runs
|
|
166
|
+
your code; react to module signals, never poll or sleep.
|
|
167
|
+
|
|
168
|
+
## 5. Standalone apps (`CustomApp`)
|
|
169
|
+
|
|
170
|
+
Only when no instrument is needed. Same (non-deprecated) lifecycle as above, `parent` is a `DockArea` or `QMainWindow`, and
|
|
171
|
+
`main()` creates the app with `mkQApp`. If instruments become necessary, move the logic to a `CustomExt`.
|
|
172
|
+
|
|
173
|
+
## 6. Models of the extensions (`models/`)
|
|
174
|
+
|
|
175
|
+
Four Dashboard extensions delegate the part that depends on your experiment to a model class that your package
|
|
176
|
+
provides: PID (`PIDModelGeneric`), Bayesian and Adaptive optimizers (`OptimizerModelGeneric` or `OptimizerModelDefault`),
|
|
177
|
+
DataMixer (`DataMixerModel`) and the Sequencer (`ChoiceModelBase`). Write a model, not an extension, when one of these
|
|
178
|
+
extensions already does the job and only the conversion of the measurements or of the outputs is specific.
|
|
179
|
+
|
|
180
|
+
- Set `models = true` in `[features]`; entry points are generated only then (`pymodaq.models`).
|
|
181
|
+
- Create the `models/` folder at the root of the package, one model per module; the module name is the name shown in
|
|
182
|
+
the extension. The model is the class defined in the module that derives from the base class of the extension. It may
|
|
183
|
+
derive from your own intermediate class, but PID and DataMixer only accepted a direct subclass of the generic base
|
|
184
|
+
before the release that follows 5.3.3, so derive directly from it if you must support older releases. The Sequencer
|
|
185
|
+
is the exception: it does not look at base classes, you register each choice model with
|
|
186
|
+
`@ChoiceModelFactory.register_choice()`.
|
|
187
|
+
- The template ships no model on purpose. Start from the working examples of
|
|
188
|
+
[`pymodaq_plugins_mockexamples`](https://github.com/PyMoDAQ/pymodaq_plugins_mockexamples) (`PIDModelBeamSteering`,
|
|
189
|
+
`PIDModelMockHeater`, `BayesianModelMock`, `DataMixerModelFit`; for the Sequencer, the choice models of
|
|
190
|
+
`pymodaq/extensions/sequencer/models`) and from the page "Models of the extensions" of the PyMoDAQ
|
|
191
|
+
documentation, which lists the methods to implement for each kind. Test a model with the mock actuators and
|
|
192
|
+
detectors of that package.
|
|
193
|
+
- Do not use the old `pid_models` flag nor the word "preset" in new code: a model script that opens the Dashboard loads
|
|
194
|
+
an experiment file.
|
|
195
|
+
|
|
196
|
+
## 7. Done checklist
|
|
197
|
+
|
|
198
|
+
- [ ] Names (package, files, classes) match and the `template` placeholders are gone.
|
|
199
|
+
- [ ] The `[features]` flags at the top of `pyproject.toml` match the folders you use (`instruments`, `extensions`,
|
|
200
|
+
`models`...): entry points are generated only for the features set to `true` (`check_plugin` warns, PMQ109).
|
|
201
|
+
- [ ] `check_plugin` passes; no `NotImplementedError` or `TODO` left in the files you wrote.
|
|
202
|
+
- [ ] Units set on all actuator values and axes; safe behaviour in `close()` and on errors.
|
|
203
|
+
- [ ] Tests added for new behaviour using a mock controller (no real hardware in CI).
|
|
204
|
+
- [ ] Docstring of each plugin class states: compatible devices, what was tested, PyMoDAQ and OS versions,
|
|
205
|
+
drivers to install.
|
|
206
|
+
- [ ] `README.rst` updated; target PyMoDAQ version stated.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Sebastien Weber <sebastien.weber@cemes.fr>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pymodaq_plugins_oceaninsight
|
|
3
|
+
Version: 5.3.0
|
|
4
|
+
Summary: Set of PyMoDAQ plugins for OceanInsight (OceanOptics) spectrometers
|
|
5
|
+
Project-URL: Homepage, https://pymodaq.cnrs.fr
|
|
6
|
+
Project-URL: Documentation , https://pymodaq.cnrs.fr
|
|
7
|
+
Project-URL: Repository , https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight
|
|
8
|
+
Author-email: Sébastien Weber <sebastien.weber@cemes.fr>
|
|
9
|
+
Maintainer-email: Sébastien Weber <sebastien.weber@cemes.fr>
|
|
10
|
+
License: The MIT License (MIT)
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2021 Sebastien Weber <sebastien.weber@cemes.fr>
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in
|
|
22
|
+
all copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
30
|
+
THE SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Natural Language :: English
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
45
|
+
Requires-Python: >=3.10
|
|
46
|
+
Requires-Dist: pymodaq-data>=5.3.0
|
|
47
|
+
Requires-Dist: pymodaq-gui>=5.3.0
|
|
48
|
+
Requires-Dist: pymodaq-utils>=5.3.1
|
|
49
|
+
Requires-Dist: pymodaq>=5.3.2
|
|
50
|
+
Requires-Dist: pythonnet
|
|
51
|
+
Requires-Dist: seabreeze
|
|
52
|
+
Description-Content-Type: text/x-rst
|
|
53
|
+
|
|
54
|
+
pymodaq_plugins_oceaninsight (Ocean Insight (Optics) Spectrometers)
|
|
55
|
+
###################################################################
|
|
56
|
+
|
|
57
|
+
.. image:: https://img.shields.io/pypi/v/pymodaq_plugins_oceaninsight.svg
|
|
58
|
+
:target: https://pypi.org/project/pymodaq_plugins_oceaninsight/
|
|
59
|
+
:alt: Latest Version
|
|
60
|
+
|
|
61
|
+
.. image:: https://readthedocs.org/projects/pymodaq/badge/?version=latest
|
|
62
|
+
:target: https://pymodaq.readthedocs.io/en/stable/?badge=latest
|
|
63
|
+
:alt: Documentation Status
|
|
64
|
+
|
|
65
|
+
.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight/workflows/Upload%20Python%20Package/badge.svg
|
|
66
|
+
:target: https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight
|
|
67
|
+
|
|
68
|
+
PyMoDAQ plugin for OceanInsight (OceanOptics) spectrometers
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Authors
|
|
72
|
+
=======
|
|
73
|
+
|
|
74
|
+
* Sebastien J. Weber
|
|
75
|
+
* Nicolas Tappy
|
|
76
|
+
* Arnaud Meyer
|
|
77
|
+
|
|
78
|
+
Instruments
|
|
79
|
+
===========
|
|
80
|
+
Below is the list of instruments included in this plugin.
|
|
81
|
+
Url: https://www.oceaninsight.com/products/spectrometers/
|
|
82
|
+
|
|
83
|
+
Viewer1D
|
|
84
|
+
++++++++
|
|
85
|
+
|
|
86
|
+
* **Omnidriver**: Control of Spectrometer using the Omnidriver library (should be installed)
|
|
87
|
+
* **Seabreeze** : If the Omnidriver library is not available, a plugin implementation based on seabreeze is provided: https://python-seabreeze.readthedocs.io/en/latest/index.html
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
pymodaq_plugins_oceaninsight (Ocean Insight (Optics) Spectrometers)
|
|
2
|
+
###################################################################
|
|
3
|
+
|
|
4
|
+
.. image:: https://img.shields.io/pypi/v/pymodaq_plugins_oceaninsight.svg
|
|
5
|
+
:target: https://pypi.org/project/pymodaq_plugins_oceaninsight/
|
|
6
|
+
:alt: Latest Version
|
|
7
|
+
|
|
8
|
+
.. image:: https://readthedocs.org/projects/pymodaq/badge/?version=latest
|
|
9
|
+
:target: https://pymodaq.readthedocs.io/en/stable/?badge=latest
|
|
10
|
+
:alt: Documentation Status
|
|
11
|
+
|
|
12
|
+
.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight/workflows/Upload%20Python%20Package/badge.svg
|
|
13
|
+
:target: https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight
|
|
14
|
+
|
|
15
|
+
PyMoDAQ plugin for OceanInsight (OceanOptics) spectrometers
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Authors
|
|
19
|
+
=======
|
|
20
|
+
|
|
21
|
+
* Sebastien J. Weber
|
|
22
|
+
* Nicolas Tappy
|
|
23
|
+
* Arnaud Meyer
|
|
24
|
+
|
|
25
|
+
Instruments
|
|
26
|
+
===========
|
|
27
|
+
Below is the list of instruments included in this plugin.
|
|
28
|
+
Url: https://www.oceaninsight.com/products/spectrometers/
|
|
29
|
+
|
|
30
|
+
Viewer1D
|
|
31
|
+
++++++++
|
|
32
|
+
|
|
33
|
+
* **Omnidriver**: Control of Spectrometer using the Omnidriver library (should be installed)
|
|
34
|
+
* **Seabreeze** : If the Omnidriver library is not available, a plugin implementation based on seabreeze is provided: https://python-seabreeze.readthedocs.io/en/latest/index.html
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from hatchling.metadata.plugin.interface import MetadataHookInterface
|
|
3
|
+
from pymodaq_utils.resources.hatch_build_plugins import update_metadata_from_toml
|
|
4
|
+
|
|
5
|
+
here = Path(__file__).absolute().parent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PluginInfoTomlHook(MetadataHookInterface):
|
|
9
|
+
def update(self, metadata: dict) -> None:
|
|
10
|
+
update_metadata_from_toml(metadata, here)
|
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[features] # defines the plugin features contained into this plugin
|
|
2
|
+
instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files)
|
|
3
|
+
extensions = false # true if plugins contains dashboard extensions
|
|
4
|
+
models = false # true if plugins contains models of the extensions (PID, optimizers, data mixer, sequencer, ...)
|
|
5
|
+
h5exporters = false # true if plugin contains custom h5 file exporters
|
|
6
|
+
scanners = false # true if plugin contains custom scan layout (daq_scan extensions)
|
|
7
|
+
|
|
8
|
+
[urls]
|
|
9
|
+
package-url = "https://github.com/PyMoDAQ/pymodaq_plugins_oceaninsight"
|
|
10
|
+
|
|
11
|
+
[project]
|
|
12
|
+
name = "pymodaq_plugins_oceaninsight"
|
|
13
|
+
description = "Set of PyMoDAQ plugins for OceanInsight (OceanOptics) spectrometers"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pymodaq>=5.3.2",
|
|
16
|
+
"pymodaq_utils>=5.3.1",
|
|
17
|
+
"pymodaq_gui>=5.3.0",
|
|
18
|
+
"pymodaq_data>=5.3.0",
|
|
19
|
+
"pythonnet",
|
|
20
|
+
"seabreeze",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
authors = [
|
|
24
|
+
{name = "Sébastien Weber", email = "sebastien.weber@cemes.fr"},
|
|
25
|
+
]
|
|
26
|
+
maintainers = [
|
|
27
|
+
{name = "Sébastien Weber", email = "sebastien.weber@cemes.fr"},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# nottodo: leave everything below as is!
|
|
31
|
+
|
|
32
|
+
dynamic = ["version", "urls", "entry-points"]
|
|
33
|
+
readme = "README.rst"
|
|
34
|
+
license = { file="LICENSE" }
|
|
35
|
+
requires-python = ">=3.10"
|
|
36
|
+
|
|
37
|
+
classifiers = [
|
|
38
|
+
"Development Status :: 5 - Production/Stable",
|
|
39
|
+
"Intended Audience :: Science/Research",
|
|
40
|
+
"License :: OSI Approved :: MIT License",
|
|
41
|
+
"Natural Language :: English",
|
|
42
|
+
"Operating System :: OS Independent",
|
|
43
|
+
"Programming Language :: Python :: 3.10",
|
|
44
|
+
"Programming Language :: Python :: 3.11",
|
|
45
|
+
"Programming Language :: Python :: 3.12",
|
|
46
|
+
"Programming Language :: Python :: 3.13",
|
|
47
|
+
"Topic :: Scientific/Engineering :: Human Machine Interfaces",
|
|
48
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
49
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
50
|
+
"Topic :: Software Development :: User Interfaces",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[build-system]
|
|
54
|
+
requires = [
|
|
55
|
+
"hatchling>=1.9.0",
|
|
56
|
+
"hatch-vcs", "toml",
|
|
57
|
+
"pymodaq_utils>=0.0.6",
|
|
58
|
+
]
|
|
59
|
+
build-backend = "hatchling.build"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.metadata.hooks.custom]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.version]
|
|
64
|
+
source = "vcs"
|
|
65
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pymodaq_utils.utils import get_version, PackageNotFoundError
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from pymodaq.utils.logger import set_logger # to be imported by other modules.
|
|
4
|
+
|
|
5
|
+
from .utils import Config
|
|
6
|
+
config = Config()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = get_version(__package__)
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__ = '0.0.0dev'
|
pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_move_plugins/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .. import set_logger
|
|
4
|
+
logger = set_logger('move_plugins', add_to_console=False)
|
|
5
|
+
|
|
6
|
+
for path in Path(__file__).parent.iterdir():
|
|
7
|
+
try:
|
|
8
|
+
if '__init__' not in str(path):
|
|
9
|
+
importlib.import_module('.' + path.stem, __package__)
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
|
|
12
|
+
pass
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from ... import set_logger
|
|
4
|
+
logger = set_logger('viewer0D_plugins', add_to_console=False)
|
|
5
|
+
|
|
6
|
+
for path in Path(__file__).parent.iterdir():
|
|
7
|
+
try:
|
|
8
|
+
if '__init__' not in str(path):
|
|
9
|
+
importlib.import_module('.' + path.stem, __package__)
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
|
|
12
|
+
pass
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from ... import set_logger
|
|
4
|
+
logger = set_logger('viewer1D_plugins', add_to_console=False)
|
|
5
|
+
|
|
6
|
+
for path in Path(__file__).parent.iterdir():
|
|
7
|
+
try:
|
|
8
|
+
if '__init__' not in str(path):
|
|
9
|
+
importlib.import_module('.' + path.stem, __package__)
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
|
|
12
|
+
pass
|
|
13
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from qtpy import QtWidgets
|
|
2
|
+
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
from pymodaq.utils.daq_utils import ThreadCommand, getLineInfo
|
|
7
|
+
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
|
|
8
|
+
import sys
|
|
9
|
+
import clr
|
|
10
|
+
from easydict import EasyDict as edict
|
|
11
|
+
|
|
12
|
+
from pymodaq_plugins_oceaninsight.utils import Config
|
|
13
|
+
|
|
14
|
+
plugin_config = Config()
|
|
15
|
+
|
|
16
|
+
omnidriver_path = plugin_config('omnidriver_path')
|
|
17
|
+
try:
|
|
18
|
+
sys.path.append(omnidriver_path)
|
|
19
|
+
clr.AddReference("NETOmniDriver-NET40")
|
|
20
|
+
import OmniDriver as omnidriver
|
|
21
|
+
|
|
22
|
+
except:
|
|
23
|
+
omnidriver = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DAQ_1DViewer_Omnidriver(DAQ_Viewer_base):
|
|
27
|
+
"""PyMoDAQ plugin controlling spectrometers from OceanOptics and using their OmniDriver wrapper ('NETOmniDriver-NET40')
|
|
28
|
+
The wrapper is interfaced using the clr package and then importing Omnidriver as a standart python package
|
|
29
|
+
https://oceanoptics.com/api/omnidriver/index.html?overview-summary.html
|
|
30
|
+
|
|
31
|
+
The plugin enables the discovery of any connected usb spectrometers and can control them in parallel. It extracts the
|
|
32
|
+
calibrated wavelength and will export datas that will be plotted with respect to this wavelengths vector.
|
|
33
|
+
|
|
34
|
+
Used methods from the wrapper:
|
|
35
|
+
------------------------------
|
|
36
|
+
setIntegrationTime
|
|
37
|
+
getIntegrationTime
|
|
38
|
+
|
|
39
|
+
See Also
|
|
40
|
+
--------
|
|
41
|
+
utility_classes.DAQ_Viewer_base
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
params = comon_parameters + [
|
|
45
|
+
{'title': 'N spectrometers:', 'name': 'Nspectrometers', 'type': 'int', 'value': 0,
|
|
46
|
+
'default': 0, 'min': 0, 'readonly': True},
|
|
47
|
+
{'title': 'Spectrometers:', 'name': 'spectrometers', 'type': 'group', 'children': []},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
hardware_averaging = True
|
|
51
|
+
|
|
52
|
+
def ini_attributes(self):
|
|
53
|
+
|
|
54
|
+
self.controller: omnidriver.NETWrapper = None
|
|
55
|
+
self.spectro_names = [] #contains the spectro name as returned from the wrapper
|
|
56
|
+
self.spectro_id = [] # contains the spectro id as set by the ini_detector method and equal to the Parameter name
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def commit_settings(self, param):
|
|
60
|
+
"""
|
|
61
|
+
"""
|
|
62
|
+
if param.name() == 'exposure_time':
|
|
63
|
+
ind_spectro = self.spectro_id.index(param.parent().name())
|
|
64
|
+
self.controller.setIntegrationTime(ind_spectro, param.value() * 1000)
|
|
65
|
+
|
|
66
|
+
param.setValue(self.controller.getIntegrationTime(ind_spectro) / 1000)
|
|
67
|
+
|
|
68
|
+
def ini_detector(self, controller=None):
|
|
69
|
+
"""
|
|
70
|
+
Initialisation procedure of the detector updating the status dictionnary.
|
|
71
|
+
|
|
72
|
+
See Also
|
|
73
|
+
--------
|
|
74
|
+
set_Mock_data, daq_utils.ThreadCommand
|
|
75
|
+
"""
|
|
76
|
+
self.ini_detector_init(controller, None)
|
|
77
|
+
|
|
78
|
+
if self.is_master:
|
|
79
|
+
self.controller = omnidriver.NETWrapper()
|
|
80
|
+
|
|
81
|
+
N = self.controller.openAllSpectrometers()
|
|
82
|
+
self.settings.child('Nspectrometers').setValue(N)
|
|
83
|
+
self.spectro_names = []
|
|
84
|
+
self.spectro_id = []
|
|
85
|
+
|
|
86
|
+
data_init = DataToExport('Spectro')
|
|
87
|
+
|
|
88
|
+
for ind_spectro in range(N):
|
|
89
|
+
name = self.controller.getName(ind_spectro)
|
|
90
|
+
self.spectro_names.append(name)
|
|
91
|
+
self.spectro_id.append('spectro{:d}'.format(ind_spectro))
|
|
92
|
+
|
|
93
|
+
exp_max = self.controller.getMaximumIntegrationTime(ind_spectro)
|
|
94
|
+
exp_min = self.controller.getMinimumIntegrationTime(ind_spectro)
|
|
95
|
+
exp = self.controller.getIntegrationTime(ind_spectro) / 1000
|
|
96
|
+
wavelengths = self.get_xaxis(ind_spectro)
|
|
97
|
+
|
|
98
|
+
data_init.append(DataFromPlugins(name=name, data=[np.zeros_like(wavelengths)],
|
|
99
|
+
dim='Data1D',
|
|
100
|
+
x_axis=Axis(data=wavelengths, label='Wavelength',
|
|
101
|
+
units='nm')))
|
|
102
|
+
|
|
103
|
+
for ind in range(2): #this is to take into account that adding it once doen't work (see pyqtgraph Parameter...)
|
|
104
|
+
try:
|
|
105
|
+
self.settings.child('spectrometers').addChild(
|
|
106
|
+
{'title': name,'name': 'spectro{:d}'.format(ind_spectro),
|
|
107
|
+
'type': 'group', 'children':[
|
|
108
|
+
{'title': 'grab spectrum:','name': 'grab', 'type': 'bool',
|
|
109
|
+
'value': True},
|
|
110
|
+
{'title': 'Exposure time (ms):','name': 'exposure_time', 'type': 'int',
|
|
111
|
+
'value': int(exp), 'min': int(exp_min/1000), 'max': int(exp_max/1000)},
|
|
112
|
+
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
except:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
QtWidgets.QApplication.processEvents()
|
|
119
|
+
|
|
120
|
+
#init viewers
|
|
121
|
+
|
|
122
|
+
if N == 0:
|
|
123
|
+
raise Exception('No detected hardware')
|
|
124
|
+
self.dte_signal_temp.emit(data_init)
|
|
125
|
+
|
|
126
|
+
initialized = True
|
|
127
|
+
info = 'Ocean Insight Spectrometers initialized'
|
|
128
|
+
return info, initialized
|
|
129
|
+
|
|
130
|
+
def get_xaxis(self, ind_spectro):
|
|
131
|
+
wavelengths_chelou = self.controller.getWavelengths(ind_spectro)
|
|
132
|
+
wavelengths = np.array([wavelengths_chelou[ind] for ind in range(len(wavelengths_chelou))])
|
|
133
|
+
|
|
134
|
+
return wavelengths
|
|
135
|
+
|
|
136
|
+
def close(self):
|
|
137
|
+
"""
|
|
138
|
+
"""
|
|
139
|
+
if self.controller is not None:
|
|
140
|
+
self.controller.closeAllSpectrometers()
|
|
141
|
+
|
|
142
|
+
def grab_data(self, Naverage=1, **kwargs):
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
dte = DataToExport('Spectro')
|
|
148
|
+
for ind_spectro in range(len(self.spectro_names)):
|
|
149
|
+
if self.settings.child('spectrometers', 'spectro{:d}'.format(ind_spectro), 'grab').value():
|
|
150
|
+
self.controller.setScansToAverage(ind_spectro, Naverage)
|
|
151
|
+
data_chelou = self.controller.getSpectrum(ind_spectro)
|
|
152
|
+
data_array = np.array([data_chelou[ind] for ind in range(len(data_chelou))])
|
|
153
|
+
dte.append(
|
|
154
|
+
DataFromPlugins(name=self.spectro_names[ind_spectro],
|
|
155
|
+
data=[data_array], dim='Data1D',
|
|
156
|
+
axes=[Axis('wavelength',
|
|
157
|
+
units='m',
|
|
158
|
+
data=self.get_xaxis(ind_spectro)
|
|
159
|
+
* 1e-9)]))
|
|
160
|
+
QtWidgets.QApplication.processEvents()
|
|
161
|
+
|
|
162
|
+
self.dte_signal.emit(dte)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), "log"]))
|
|
167
|
+
|
|
168
|
+
def stop(self):
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
for ind_spec, name in enumerate(self.spectro_names):
|
|
173
|
+
self.controller.stopAveraging(ind_spec)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
main(__file__, init=False)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from easydict import EasyDict as edict
|
|
3
|
+
from pymodaq.utils.daq_utils import ThreadCommand, getLineInfo
|
|
4
|
+
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
|
|
5
|
+
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
|
|
6
|
+
from timeit import timeit
|
|
7
|
+
|
|
8
|
+
# explicitly request cseabreeze
|
|
9
|
+
import seabreeze
|
|
10
|
+
seabreeze.use('cseabreeze')
|
|
11
|
+
from seabreeze.spectrometers import Spectrometer, list_devices
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DAQ_1DViewer_Seabreeze(DAQ_Viewer_base):
|
|
15
|
+
"""PyMoDAQ 1DViewer class for devices supported by the Seabreeze library."""
|
|
16
|
+
|
|
17
|
+
# Upon initialisation
|
|
18
|
+
devices = list_devices()
|
|
19
|
+
|
|
20
|
+
# Supports pseudo hardware-averaging
|
|
21
|
+
hardware_averaging = True
|
|
22
|
+
|
|
23
|
+
# Adjustable parameters
|
|
24
|
+
params = comon_parameters + [
|
|
25
|
+
{'title': 'Device:', 'name': 'device', 'type': 'list', 'limits': devices},
|
|
26
|
+
{'title': 'Integration (ms):', 'name': 'integration', 'type': 'float', 'value': 1.0},
|
|
27
|
+
{'title': 'Advanced:', 'name': 'advanced', 'type': 'group', 'children': [
|
|
28
|
+
{'title': 'Serial Number:', 'name': 'serial_number', 'type': 'str', 'value': "", 'readonly': True},
|
|
29
|
+
{'title': 'Non Linearity correction:', 'name': 'correct_non_linearity', 'type': 'bool', 'value': False},
|
|
30
|
+
{'title': 'Max Intensity', 'name': 'max_intensity', 'type': "float", 'value': 65535, 'readonly': True},
|
|
31
|
+
{'title': 'Pixels:', 'name': 'pixels', 'type': 'int', 'value': 2048, 'readonly': True},
|
|
32
|
+
{'title': 'Dark Channels:', 'name': 'dark_channels', 'type': 'str', 'value': '', 'readonly': True},
|
|
33
|
+
{'title': 'Readout Time (ms)', 'name': 'readout_time', 'type': 'float', 'value': 666, 'readonly': True},
|
|
34
|
+
]}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def ini_attributes(self):
|
|
38
|
+
self.controller: Spectrometer = None
|
|
39
|
+
|
|
40
|
+
def commit_settings(self, param):
|
|
41
|
+
"""
|
|
42
|
+
"""
|
|
43
|
+
if param.name() == "integration":
|
|
44
|
+
self.controller.integration_time_micros(param.value() * 1000)
|
|
45
|
+
# There is no way to get feedback from the spectrometer on the currently set integration time
|
|
46
|
+
# elif ...
|
|
47
|
+
##
|
|
48
|
+
|
|
49
|
+
def ini_detector(self, controller=None):
|
|
50
|
+
"""Detector communication initialization
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
controller: (object) custom object of a PyMoDAQ plugin (Slave case). None if only one detector by controller (Master case)
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
self.status (edict): with initialization status: three fields:
|
|
59
|
+
* info (str)
|
|
60
|
+
* controller (object) initialized controller
|
|
61
|
+
*initialized: (bool): False if initialization failed otherwise True
|
|
62
|
+
"""
|
|
63
|
+
if self.is_master:
|
|
64
|
+
# From what I understand this will just get the spectro currently selected in the list
|
|
65
|
+
dvc = self.settings['device']
|
|
66
|
+
self.controller = Spectrometer(dvc)
|
|
67
|
+
#self.controller.open()
|
|
68
|
+
|
|
69
|
+
else:
|
|
70
|
+
self.controller = controller
|
|
71
|
+
|
|
72
|
+
# Oceanoptics spectrometers (at least the ones i Know) have fixed axis
|
|
73
|
+
# get index of inactive pixels
|
|
74
|
+
dark_indices = self.controller.f.spectrometer.get_electric_dark_pixel_indices()
|
|
75
|
+
self.settings.child('advanced').child('dark_channels').setValue(", ".join([str(x) for x in dark_indices]))
|
|
76
|
+
# get the x_axis
|
|
77
|
+
data_x_axis = self.controller.wavelengths() # Way to get the x axis
|
|
78
|
+
# keep range without dark pixels
|
|
79
|
+
x_indices = np.arange(data_x_axis.shape[0])
|
|
80
|
+
self.valid_range = np.setdiff1d(x_indices, dark_indices)
|
|
81
|
+
data_x_axis = data_x_axis[self.valid_range]
|
|
82
|
+
# generate PyMoDAQ axis
|
|
83
|
+
self.x_axis = Axis(data=data_x_axis * 1e-9, label='wavelength', units='m', index=0)
|
|
84
|
+
|
|
85
|
+
# Get the name
|
|
86
|
+
specname = f"Ocean Insight {self.controller.model}"
|
|
87
|
+
|
|
88
|
+
# initialize viewers pannel with the future type of data
|
|
89
|
+
|
|
90
|
+
self.data_grabed_signal_temp.emit([
|
|
91
|
+
DataFromPlugins(name=specname,
|
|
92
|
+
data=[np.zeros_like(data_x_axis)],
|
|
93
|
+
dim='Data1D',
|
|
94
|
+
labels=['Intensity'],
|
|
95
|
+
axes=[self.x_axis]), ])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Update the parameters
|
|
99
|
+
# Here we need to do a few things. Get the integration time limits and set it in the settings
|
|
100
|
+
tlimits = np.array(self.controller.integration_time_micros_limits) / 1000
|
|
101
|
+
self.settings.child('integration').setLimits(tlimits)
|
|
102
|
+
# Here we need to update the advanced parameters
|
|
103
|
+
advanced_settings = self.settings.child('advanced')
|
|
104
|
+
sn = self.controller.serial_number
|
|
105
|
+
advanced_settings.child('serial_number').setValue(sn)
|
|
106
|
+
# non linearity coefficients
|
|
107
|
+
nlc_feat = self.controller.f.nonlinearity_coefficients
|
|
108
|
+
if nlc_feat is not None and not any(np.isnan(nlc_feat.get_nonlinearity_coefficients())):
|
|
109
|
+
advanced_settings.child('correct_non_linearity').setValue(True)
|
|
110
|
+
else:
|
|
111
|
+
advanced_settings.child('correct_non_linearity').setValue(False)
|
|
112
|
+
advanced_settings.child('correct_non_linearity').setOpts(enabled=False)
|
|
113
|
+
|
|
114
|
+
# measure the readout time
|
|
115
|
+
Nperf = 200
|
|
116
|
+
self.settings.child('integration').setValue(tlimits[0]) # Update the parameter value to the lower limit
|
|
117
|
+
self.controller.integration_time_micros(tlimits[0]*1000) # Set the readout time to lower limit
|
|
118
|
+
perf = timeit(lambda: self.controller.intensities(), number=Nperf) # time the execution of code in [s]
|
|
119
|
+
self.settings.child('advanced').child('readout_time').setValue(1000*perf/Nperf) # set the settings
|
|
120
|
+
|
|
121
|
+
##############################
|
|
122
|
+
|
|
123
|
+
info = f"Initialized {specname} spectrometer"
|
|
124
|
+
initialized = True
|
|
125
|
+
return info, initialized
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
"""
|
|
129
|
+
Terminate the communication protocol
|
|
130
|
+
"""
|
|
131
|
+
if self.controller is not None:
|
|
132
|
+
self.controller.close()
|
|
133
|
+
|
|
134
|
+
def grab_data(self, Naverage = 1, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
Naverage: (int) Number of hardware averaging
|
|
139
|
+
kwargs: (dict) of others optionals arguments
|
|
140
|
+
"""
|
|
141
|
+
nlc = self.settings.child('advanced').child('correct_non_linearity').value()
|
|
142
|
+
#c0 = self.settings.child('advanced').child('dark_channels').value()
|
|
143
|
+
|
|
144
|
+
# synchrone version (blocking function)
|
|
145
|
+
# Pseudo-hardware-averaging
|
|
146
|
+
if Naverage > 1:
|
|
147
|
+
data = [self.controller.intensities(correct_nonlinearity=nlc)[self.valid_range]
|
|
148
|
+
for i in range(Naverage)]
|
|
149
|
+
data = np.array(data)
|
|
150
|
+
data = data.mean(0)
|
|
151
|
+
# Otherwise normal single-acquisition
|
|
152
|
+
else:
|
|
153
|
+
data = self.controller.intensities(correct_nonlinearity=nlc)[self.valid_range]
|
|
154
|
+
|
|
155
|
+
self.dte_signal.emit(DataToExport('Spectro', data=[
|
|
156
|
+
DataFromPlugins(name='oceanseabreeze', data=[data], dim='Data1D',
|
|
157
|
+
labels=['spectrum'], axes=[self.x_axis.copy()])]))
|
|
158
|
+
|
|
159
|
+
def stop(self):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == '__main__':
|
|
164
|
+
main(__file__, init=False)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from ... import set_logger
|
|
4
|
+
logger = set_logger('viewer2D_plugins', add_to_console=False)
|
|
5
|
+
|
|
6
|
+
for path in Path(__file__).parent.iterdir():
|
|
7
|
+
try:
|
|
8
|
+
if '__init__' not in str(path):
|
|
9
|
+
importlib.import_module('.' + path.stem, __package__)
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from pymodaq.utils.logger import set_logger
|
|
4
|
+
logger = set_logger('viewerND_plugins', add_to_console=False)
|
|
5
|
+
|
|
6
|
+
for path in Path(__file__).parent.iterdir():
|
|
7
|
+
try:
|
|
8
|
+
if '__init__' not in str(path):
|
|
9
|
+
importlib.import_module('.' + path.stem, __package__)
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e)))
|
|
12
|
+
pass
|
|
13
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created the 31/08/2023
|
|
4
|
+
|
|
5
|
+
@author: Sebastien Weber
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pymodaq.utils.config import BaseConfig, USER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Config(BaseConfig):
|
|
13
|
+
"""Main class to deal with configuration values for this plugin"""
|
|
14
|
+
config_template_path = Path(__file__).parent.joinpath('resources/config_template.toml')
|
|
15
|
+
config_name = f"config_{__package__.split('pymodaq_plugins_')[1]}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Acceptance tests of the plugin, provided by PyMoDAQ (from version 5.3.2).
|
|
2
|
+
|
|
3
|
+
They check the package structure, the entry points, the naming of the instrument modules and classes, the mandatory
|
|
4
|
+
attributes and methods of the plugin classes, and apply the static rules of PyMoDAQ on the sources
|
|
5
|
+
(see https://pymodaq.cnrs.fr/en/latest/developer_folder/instrument_plugins.html#testing-your-plugin).
|
|
6
|
+
|
|
7
|
+
The unfinished parts of the plugin (TODO comments, placeholders) are listed but do not fail the tests, set
|
|
8
|
+
``fail_on = 'todo'`` before a release. Add your own tests, specific to your instruments, in other files.
|
|
9
|
+
"""
|
|
10
|
+
import importlib.util
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
available = importlib.util.find_spec('pymodaq.utils.plugin_testing') is not None
|
|
16
|
+
except ModuleNotFoundError: # pymodaq is not installed
|
|
17
|
+
available = False
|
|
18
|
+
if not available:
|
|
19
|
+
pytest.skip('The plugin acceptance checks need PyMoDAQ >= 5.3.2', allow_module_level=True)
|
|
20
|
+
|
|
21
|
+
from pymodaq.utils.plugin_testing import PluginPackageChecks
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestPlugin(PluginPackageChecks):
|
|
25
|
+
# package_name = 'pymodaq_plugins_xxxx' # only if it cannot be found from the pyproject.toml and the package folder
|
|
26
|
+
fail_on = 'error' # 'error', 'warning' or 'todo' (also fail on the unfinished parts)
|
|
27
|
+
strict_imports = False # True: also fail when a module cannot be imported (missing dependency or driver); the
|
|
28
|
+
# modules of an instrument whose driver cannot be installed on the CI runner are then skipped, not failed
|