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.
Files changed (27) hide show
  1. pymodaq_plugins_oceaninsight-5.3.0/.gitattributes +2 -0
  2. pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/compatibility.yml +15 -0
  3. pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/python-publish.yml +11 -0
  4. pymodaq_plugins_oceaninsight-5.3.0/.github/workflows/tests.yml +8 -0
  5. pymodaq_plugins_oceaninsight-5.3.0/.gitignore +116 -0
  6. pymodaq_plugins_oceaninsight-5.3.0/AGENTS.md +206 -0
  7. pymodaq_plugins_oceaninsight-5.3.0/CLAUDE.md +3 -0
  8. pymodaq_plugins_oceaninsight-5.3.0/LICENSE +21 -0
  9. pymodaq_plugins_oceaninsight-5.3.0/PKG-INFO +87 -0
  10. pymodaq_plugins_oceaninsight-5.3.0/README.rst +34 -0
  11. pymodaq_plugins_oceaninsight-5.3.0/hatch_build.py +10 -0
  12. pymodaq_plugins_oceaninsight-5.3.0/icon.ico +0 -0
  13. pymodaq_plugins_oceaninsight-5.3.0/pyproject.toml +65 -0
  14. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/__init__.py +12 -0
  15. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_move_plugins/__init__.py +13 -0
  16. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/__init__.py +3 -0
  17. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_0D/__init__.py +13 -0
  18. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/__init__.py +13 -0
  19. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/daq_1Dviewer_Omnidriver.py +177 -0
  20. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_1D/daq_1Dviewer_Seabreeze.py +164 -0
  21. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_2D/__init__.py +14 -0
  22. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/daq_viewer_plugins/plugins_ND/__init__.py +13 -0
  23. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/hardware/__init__.py +0 -0
  24. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/resources/__init__.py +0 -0
  25. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/resources/config_template.toml +3 -0
  26. pymodaq_plugins_oceaninsight-5.3.0/src/pymodaq_plugins_oceaninsight/utils.py +15 -0
  27. pymodaq_plugins_oceaninsight-5.3.0/tests/test_plugin_package_structure.py +28 -0
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -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,3 @@
1
+ # CLAUDE.md
2
+
3
+ Read and follow [AGENTS.md](AGENTS.md): it holds all the guidance for AI coding assistants working in this repository.
@@ -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)
@@ -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'
@@ -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
+
@@ -0,0 +1,3 @@
1
+ title = "this is the configuration file of the plugin OceanInsight"
2
+
3
+ omnidriver_path = 'C:\\Program Files\\Ocean Optics\\OmniDriver\\OOI_HOME'
@@ -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