From 42d51378235e273beb01dfc653cc4a1b68a35b15 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Tue, 13 Feb 2024 14:03:16 +0100 Subject: [PATCH 01/10] Adding example rtd config file --- .readthedocs.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..1a4992866 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# python: +# install: +# - requirements: docs/requirements.txt \ No newline at end of file From 1011e404709cfeb9db169ed45befa310efe28d7a Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Tue, 13 Feb 2024 19:10:20 +0100 Subject: [PATCH 02/10] Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout. --- .gitignore | 46 +++++++--- docs/404.md | 1 + docs/Makefile | 20 +++++ docs/_config.yml | 4 - docs/conf.py | 44 ++++++++++ docs/design_concepts/config_options.md | 10 +-- docs/design_concepts/hardware_interface.md | 11 +-- docs/design_concepts/logging.md | 10 +-- docs/design_concepts/measurement_modules.md | 18 ++-- docs/design_concepts/remote_modules.md | 4 +- docs/design_concepts/status_variables.md | 4 +- docs/index.md | 56 +++++-------- docs/license.md | 4 +- docs/make.bat | 35 ++++++++ .../creating_releases.md | 1 + docs/setup/startup.md | 2 +- pyproject.toml | 83 ++++++++++++++++++- 17 files changed, 264 insertions(+), 89 deletions(-) create mode 100644 docs/Makefile delete mode 100644 docs/_config.yml create mode 100644 docs/conf.py create mode 100644 docs/make.bat diff --git a/.gitignore b/.gitignore index 3e3ae1c07..cd57fb1f4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,19 +16,31 @@ .*.swp .DS_store -# Local config directories -/*.egg-info - -# Simulated Subversion default ignores end here -# The contents of the svn:ignore property on the branch root. -/crash.log -/tempLog.txt -/.idea -/thirdparty -/documentation/generated -/documentation/images -/Data +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +<<<<<<< HEAD # log files *.log *.log.* @@ -59,6 +71,8 @@ share/python-wheels/ .installed.cfg *.egg +======= +>>>>>>> 26647177 (Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout.) # Installer logs pip-log.txt pip-delete-this-directory.txt @@ -80,8 +94,11 @@ cover/ # Sphinx documentation docs/_build/ +<<<<<<< HEAD docs/_autosummary/ docs/_static/ +======= +>>>>>>> 26647177 (Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout.) # Jupyter Notebook .ipynb_checkpoints @@ -107,9 +124,12 @@ venv.bak/ .mypy_cache/ .dmypy.json dmypy.json +<<<<<<< HEAD # ruff .ruff_cache/ # vscode settings -.vscode/ \ No newline at end of file +.vscode/ +======= +>>>>>>> 26647177 (Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout.) diff --git a/docs/404.md b/docs/404.md index 7ccfc813a..e5dc4fdb5 100644 --- a/docs/404.md +++ b/docs/404.md @@ -2,6 +2,7 @@ permalink: /404.html title: Error 404 description: qudi-core +orphan: True --- # Uh... have you tried turning it off and on again? diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..d4bb2cbb9 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 358b3316c..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,4 +0,0 @@ -theme: jekyll-theme-cayman -title: qudi-core -description: A framework for modular measurement applications -markdown: GFM diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..568d363c8 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,44 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'qudi-core' +copyright = '2024, Ulm IQO' +author = 'Ulm IQO' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + + # 'IPython.sphinxext.ipython_console_highlighting', + # 'nbsphinx', +extensions = [ + 'IPython.sphinxext.ipython_directive', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.viewcode', + 'numpydoc', + 'myst_parser', +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] + +autosummary_generate = True + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'pydata_sphinx_theme' +# html_theme = 'sphinx_book_theme' +html_static_path = ['_static'] + +numpydoc_show_class_members = False +numpydoc_show_inherited_class_members = False +numpydoc_class_members_toctree = False + +myst_heading_anchors = 4 + diff --git a/docs/design_concepts/config_options.md b/docs/design_concepts/config_options.md index df72589c1..b17b8bc93 100644 --- a/docs/design_concepts/config_options.md +++ b/docs/design_concepts/config_options.md @@ -77,14 +77,14 @@ logic: ... ``` -#### name +### name Please note here that the variable name in the measurement module definition is `_my_config_option`, while the name given in the config file is `my_config_option` (without underscore). This is possible because of the optional `name` argument of `ConfigOption`. This argument specifies the field name of the config option in the qudi configuration and can be any YAML-compatible string as long as it is unique within a measurement module. -#### default +### default This example is also defining an optional `default` value for the config option. If you specify a default value, this config option is considered optional, i.e. if you do not provide the config option via qudi configuration, it will be initialized to this default value instead. @@ -92,7 +92,7 @@ Non-optional config options (omitting the `default` argument) will cause the mea raise an exception during initialization if the corresponding field is not specified in the qudi configuration. -#### missing +### missing The optional `missing` argument can be used to define the behaviour in case the config option is missing from the configuration and has a default value. Ignored for non-optional config options. Possible argument values are: @@ -104,13 +104,13 @@ Possible argument values are: | `'warn'` | Use default value but also logs a warning about the missing config option. | | `'error'` | Fail to initialize the module with an exception. Same as for non-optional config options. | -#### checker +### checker If you want to establish sanity checking for your config option at module initialization, you can provide a static function to the optional `checker` argument of `ConfigOption`. This function should accept a single argument (the configured value coming from the YAML loader) and return a boolean indicating if the check has passed (`True`) or not. -#### constructor +### constructor Since config options must be provided via YAML format you are limited in what data types can be configured. The qudi YAML loader currently supports any native Python builtin type and numpy arrays. diff --git a/docs/design_concepts/hardware_interface.md b/docs/design_concepts/hardware_interface.md index f140e8a84..7cc1c6d2f 100644 --- a/docs/design_concepts/hardware_interface.md +++ b/docs/design_concepts/hardware_interface.md @@ -21,14 +21,15 @@ So every class that inherits the same interface is guaranteed to provide at leas methods and properties defined in the interface. Since the interface subclass that implements all the abstract members is also a subclass of `Base`, -this class is then called a [hardware module](). +this class is then called a [hardware module](../404.md). -See also the detailed [qudi modules](qudi_modules.md) documentation if you want to know more about + +See also the detailed [qudi modules](https://github.com/Ulm-IQO/qudi-iqo-modules/blob/main/docs/installation_guide.md) documentation if you want to know more about what defines a qudi module and the respective inheritance trees. It's always good to have a look at some example... so here is a simple interface class for you. -### Example Interface: +## Example Interface: ```python from abc import abstractmethod from qudi.core import Base @@ -65,7 +66,7 @@ Note also that it inherits `Base`. An actual hardware module implementation satisfying this interface could look like below, assuming you placed the above code in `qudi/interface/my_simple_interface.py`. -### Hardware Implementation: +## Hardware Implementation: ```python from qudi.interface.my_simple_interface import MySimpleInterface @@ -114,7 +115,7 @@ class in accordance with good programming practices. > hardware class internally. Consequentially, you should consider making them protected or even > private, i.e. add single `_` or double underscore `__` name prefix, respectively. -### Why Do All This? +## Why Do All This? An abstract interface class defines a common set of methods and properties to control and monitor a certain generalized type of hardware (CW microwave sources, lasers, AWGs, etc.). diff --git a/docs/design_concepts/logging.md b/docs/design_concepts/logging.md index 70dcd6fe5..c81140411 100644 --- a/docs/design_concepts/logging.md +++ b/docs/design_concepts/logging.md @@ -42,24 +42,24 @@ documentation: | info | 20 | | debug | 10 | -#### critical +### critical When a log record of level "critical" and higher is detected qudi will immediately attempt to kill all running tasks and processes. In 99.99% of the cases there should be no reason to log a record of this level. So don't do it unless you have very good reasons and know what you are doing. -#### error +### error Records of level "error" or higher should usually only be logged while handling an exception (see: [exception handling guidelines](../404.md)). Unhandled exceptions will automatically be logged on the "error" level. -#### warn +### warn Warning messages can be used as a tool by application programmers to point out possible problems, e.g. an occurring edge case that is not yet well handled and often leads to errors. -#### info +### info Info messages should be used by an application programmer to inform the user of major events or milestones in the intended program execution flow. @@ -68,7 +68,7 @@ milestones in the intended program execution flow. > There is a fine line between spam and useful information density. > Think carefully what to log and use "debug" level while developing code and debugging. -#### debug +### debug Use debug messages for information only interesting for programmers, e.g. to simplify debugging. Records of this level and below are ignored by default and are not logged unless you run qudi in debug mode (command line argument `--debug`). diff --git a/docs/design_concepts/measurement_modules.md b/docs/design_concepts/measurement_modules.md index 6c4b0245b..68b858b72 100644 --- a/docs/design_concepts/measurement_modules.md +++ b/docs/design_concepts/measurement_modules.md @@ -53,7 +53,7 @@ They must connect to at least one logic module in order to provide a graphical i All qudi measurement module classes are descendants of the abstract `qudi.core.module.Base` class which provides the following features: -#### 1. Logging +### 1. Logging Easy access to the qudi logging facility via their own logger object property `log`. It can be used with all major log levels: ```python @@ -65,7 +65,7 @@ It can be used with all major log levels: # qudi shutdown attempt ``` -#### 2. Finite State Machine +### 2. Finite State Machine A very simple finite state machine (FSM) that can be accessed via property `module_state`: ![FSM state diagram](../images/module_fsm_diagram.svg) @@ -76,7 +76,7 @@ It can also be accessed by other entities in order to check if the measurement m or `locked` state (i.e. if the module is busy). The name of the current state can be polled by calling the FSM object: `.module_state()`. -#### 3. Thread Management +### 3. Thread Management Logic modules (subclass of `qudi.core.module.LogicBase`) will run by default in their own thread. Hardware and GUI modules will not live in their own threads by default. This is why it is so important to facilitate [inter-module communication](../404.md) mainly with Qt Signals in order @@ -102,7 +102,7 @@ class MyExampleModule(Base): ``` Spawning and joining threads is handled automatically by the qudi [thread manager](../404.md). -#### 4. Balloon and Pop-Up Messaging +### 4. Balloon and Pop-Up Messaging An easy way to notify the user with a message independent of the logging facility is provided via the two utility methdos `_send_balloon_message` and `_send_pop_up_message`. @@ -115,7 +115,7 @@ Of course pop-up messages will not work if qudi is running in headless mode. In message will be printed out. This is also the behaviour if balloon messages are not supported by the OS. -#### 5. Status Variables +### 5. Status Variables Status variables (`qudi.core.module.StatusVar` members) are automatically dumped and loaded upon deactivation and activation of the measurement module, respectively. @@ -129,7 +129,7 @@ the type and size of the variables. So think carefully before using manual dumpi See also the [qudi status variable documentation](../404.md). -#### 6. Static Configuration +### 6. Static Configuration Using qudi config options (`qudi.core.module.ConfigOption` members) one can facilitate static configuration of your measurement modules. Upon instantiation of a module, `ConfigOption` meta @@ -141,14 +141,14 @@ NOT each time the module is activated. See also the [qudi configuration option documentation](../404.md). -#### 7. Measurement Module Interconnection +### 7. Measurement Module Interconnection You can define other measurement modules that can be accessed via `Connector` meta object members. The qudi module manager will automatically load and activate dependency modules according to the configuration and connect them to the module upon activation. See also the [section further below](#inter-module-communication) for more info. -#### 8. Meta Information +### 8. Meta Information Various read-only properties providing meta-information about the module: | property | description | @@ -158,7 +158,7 @@ Various read-only properties providing meta-information about the module: | `module_uuid` | A unique `UUID` that can be used to identify the module unambiguously | | `module_default_data_dir` | The full path to the default module data directory. Can be overridden by module implementation. | -#### 9. Access to qudi main instance +### 9. Access to qudi main instance Each measurement module holds a (weak) reference to the [`qudi.core.application.Qudi`](../404.md) singleton instance. This object holds references to all running core facilities like the currently loaded diff --git a/docs/design_concepts/remote_modules.md b/docs/design_concepts/remote_modules.md index 93b19d5e1..fa6b27f03 100644 --- a/docs/design_concepts/remote_modules.md +++ b/docs/design_concepts/remote_modules.md @@ -2,7 +2,7 @@ Qudi supports accessing modules of a qudi instance that is running on a different (remote) computer within the same LAN. A possible configuration for this looks like: -### Server +## Server global: @@ -17,7 +17,7 @@ Qudi supports accessing modules of a qudi instance that is running on a differen options: ... -### Client +## Client global: force_remote_calls_by_value: True diff --git a/docs/design_concepts/status_variables.md b/docs/design_concepts/status_variables.md index 886298059..6e79b66c7 100644 --- a/docs/design_concepts/status_variables.md +++ b/docs/design_concepts/status_variables.md @@ -66,7 +66,7 @@ class MyExampleLogic(LogicBase): ... ``` -#### constructor & representer +### constructor & representer Since status variables are dumped as YAML file you are limited in what data types can be stored. Qudi YAML dumper and loader currently supports any native Python builtin type and numpy arrays. @@ -114,7 +114,7 @@ class MyExampleLogic(LogicBase): Since these conversion functions are usually static (as the example above also shows), you could also combine that with the `@staticmethod` decorator. But this is not necessary and just good style. -#### name +### name There is an optional `name` argument for `StatusVar`. The name given here is used by the YAML dumper as field name for the variable data. So the `name` argument can be used to store the status variable under a different (e.g. better readable) name in the app status file that is created. diff --git a/docs/index.md b/docs/index.md index 149b4a832..cb289105e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,44 +1,25 @@ +# Welcome to the Qudi Core Documentation +---- + +[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) + +```{toctree} --- -layout: default -title: qudi-core -has_children: true +maxdepth: 2 +glob: --- -# Welcome to the qudi documentation +getting_started.md +license.md ---- -[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +setup/installation.md +setup/startup.md +setup/jupyter.md + +design_concepts/* -### [Getting Started](getting_started.md) - -### [License](license.md) - -### Setup -- [Installation](setup/installation.md) -- [Startup](setup/startup.md) -- [Jupyter Notebook Integration](setup/jupyter.md) - -### Design Concepts -- [Measurement Modules (Hardware/Logic/GUI)](design_concepts/measurement_modules.md) -- [Status Variables](design_concepts/status_variables.md) -- [Config Options](design_concepts/config_options.md) -- [Connectors](design_concepts/connectors.md) -- [Configuration](design_concepts/configuration.md) -- [Hardware Interface](design_concepts/hardware_interface.md) -- Module Interfuse -- [Logging](design_concepts/logging.md) -- Integrated IPython Kernel -- [Remote Modules](design_concepts/remote_modules.md) -- Thread Manager -- Module Manager -- Main Application -- Module Servers -- Task Runner -- [Data Storage](core_elements/data_storage.md) -- Data Fit Models - -### Programming Guidelines -- Code Style + +``` diff --git a/docs/license.md b/docs/license.md index c19c46166..27ad27fb6 100644 --- a/docs/license.md +++ b/docs/license.md @@ -41,7 +41,7 @@ If not, see . Some parts of qudi are derived work from other projects licensed under the Modified BSD License and The MIT License. Their respective license and copyright disclaimers are listed below: -#### 1. [IPython](https://github.com/ipython/) +### 1. [IPython](https://github.com/ipython/) ``` This project is licensed under the terms of the Modified BSD License (also known as New or Revised or 3-Clause BSD), as follows: @@ -76,7 +76,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` -#### 2. [ACQ4](https://github.com/acq4/acq4) +### 2. [ACQ4](https://github.com/acq4/acq4) ``` Copyright (c) 2013, Luke Campagnola diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..32bb24529 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/programming_guidelines/creating_releases.md b/docs/programming_guidelines/creating_releases.md index 7a3416430..a8cd4df60 100644 --- a/docs/programming_guidelines/creating_releases.md +++ b/docs/programming_guidelines/creating_releases.md @@ -1,3 +1,4 @@ +--- layout: default title: qudi-core --- diff --git a/docs/setup/startup.md b/docs/setup/startup.md index 6eccb5f39..6e7d1720d 100644 --- a/docs/setup/startup.md +++ b/docs/setup/startup.md @@ -26,7 +26,7 @@ There are also two additional supported ways to run qudi: This is especially helpful when you have qudi installed in development mode and want to run qudi from within an IDE like e.g. PyCharm. -### Command Line Arguments +## Command Line Arguments The above mentioned commands takes several optional command line arguments to pass to qudi upon startup: diff --git a/pyproject.toml b/pyproject.toml index 374b58cbf..dbbf6a914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,81 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel" -] +requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" + +[project] +name = "qudi-core" +description = "A modular measurement application framework" +readme = "README.md" +keywords = [ + 'qudi', + 'diamond', + 'quantum', + 'confocal', + 'automation', + 'experiment', + 'measurement', + 'framework', + 'lab', + 'laboratory', + 'instrumentation', + 'instrument', + 'modular', +] +license = { text = "LGPLv3" } +classifiers = [ + 'Development Status :: 5 - Production/Stable', + + 'Environment :: Win32 (MS Windows)', + 'Environment :: X11 Applications', + 'Environment :: MacOS X', + + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Intended Audience :: End Users/Desktop', + + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + + 'Natural Language :: English', + + 'Operating System :: Microsoft :: Windows :: Windows 8', + 'Operating System :: Microsoft :: Windows :: Windows 8.1', + 'Operating System :: Microsoft :: Windows :: Windows 10', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Unix', + 'Operating System :: POSIX :: Linux', + + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + + 'Topic :: Scientific/Engineering', + 'Topic :: Software Development :: Libraries :: Application Frameworks', + 'Topic :: Software Development :: User Interfaces', +] +dependencies = [ + "wheel>=0.37.0", + "cycler>=0.10.0", + "entrypoints>=0.3", + "fysom>=2.1.6", + "GitPython>=3.1.24", + "jupyter>=1.0.0", + "jupytext>=1.13.0", + "lmfit>=1.0.3", + "matplotlib>=3.4.3", + "numpy>=1.21.3", + "pyqtgraph>=0.13.0", + "PySide2==5.15.2.1", + "rpyc>=5.0.1", + "ruamel.yaml>=0.17.16", + "scipy>=1.7.1", + "jsonschema>=4.2.1", +] +version = "VERSION" + +[project.optional-dependencies] +sphinx = ["Sphinx==7.2.6"] +numpydoc = ["numpydoc==1.6.0"] +sphinx-book-theme = ["sphinx-book-theme==1.1.1"] +# sphinx-rtd-theme = ["sphinx-rtd-theme==2.0.0"] +myst-parser = ["myst-parser==2.0.0"] +# nbsphinx = "^0.8.8" From 6c95b3c760dbfa4fd4bba003460b2e31ed1eece3 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 08:35:08 +0100 Subject: [PATCH 03/10] Adding dependencies to build docs --- .readthedocs.yaml | 6 +++--- docs/requirements-docs.txt | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 docs/requirements-docs.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1a4992866..c21538684 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -21,6 +21,6 @@ sphinx: # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -# python: -# install: -# - requirements: docs/requirements.txt \ No newline at end of file +python: + install: + - requirements: docs/requirements-docs.txt diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt new file mode 100644 index 000000000..026451530 --- /dev/null +++ b/docs/requirements-docs.txt @@ -0,0 +1,20 @@ +wheel>=0.37.0 +cycler>=0.10.0 +entrypoints>=0.3 +fysom>=2.1.6 +GitPython>=3.1.24 +jupyter>=1.0.0 +jupytext>=1.13.0 +lmfit>=1.0.3 +matplotlib>=3.4.3 +numpy>=1.21.3 +pyqtgraph>=0.13.0 +PySide2==5.15.2.1 +rpyc>=5.0.1 +ruamel.yaml>=0.17.16 +scipy>=1.7.1 +jsonschema>=4.2.1 +Sphinx==7.2.6 +numpydoc==1.6.0 +pydata_sphinx_theme==0.15.2 +myst-parser==2.0.0 From 39dbbfa9c73633956b30695f03dda93183e487c9 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 08:41:29 +0100 Subject: [PATCH 04/10] Pyside2 does not support python 3.11 it seems so rtd build fails. Funnil, Pyside2 docs say that Python 3.10 is not supported for Pyside2==5.15.2.1 which is the version that is pinned in setup.py but qudi installation docs say it works with Python 3.10 and I myself use Python 3.10. So let's just try Python 3.10 --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c21538684..8730c2926 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,7 +8,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.10" # Build documentation in the "docs/" directory with Sphinx sphinx: From 6f7267290f5e1b0f65b847b05c1a50ff3a1cc322 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 11:08:34 +0100 Subject: [PATCH 05/10] Unifying everything into pyproject.toml and testing if rtd builds from pyproject.toml too using the pre-build job --- .readthedocs.yaml | 12 +++++++++--- VERSION | 1 - pyproject.toml | 13 ++++++++----- setup.py | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) delete mode 100644 VERSION diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8730c2926..9f6a21429 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -21,6 +21,12 @@ sphinx: # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: docs/requirements-docs.txt +# python: +# install: +# - requirements: docs/requirements-docs.txt + +# Install the project in editable mode. Avoid needing a requirements.txt +jobs: + pre-build: + steps: + - pip install -e ".[dev-docs]" diff --git a/VERSION b/VERSION deleted file mode 100644 index 5eaba81aa..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.5.2.dev0 diff --git a/pyproject.toml b/pyproject.toml index dbbf6a914..4001ce706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "qudi-core" description = "A modular measurement application framework" +version = "1.5.1.dev0" readme = "README.md" keywords = [ 'qudi', @@ -70,12 +71,14 @@ dependencies = [ "scipy>=1.7.1", "jsonschema>=4.2.1", ] -version = "VERSION" [project.optional-dependencies] -sphinx = ["Sphinx==7.2.6"] -numpydoc = ["numpydoc==1.6.0"] -sphinx-book-theme = ["sphinx-book-theme==1.1.1"] +dev-docs = [ + "Sphinx==7.2.6", + "numpydoc==1.6.0", + "pydata_sphinx_theme==0.15.2", + "myst-parser==2.0.0", +] +# sphinx-book-theme = ["sphinx-book-theme==1.1.1"] # sphinx-rtd-theme = ["sphinx-rtd-theme==2.0.0"] -myst-parser = ["myst-parser==2.0.0"] # nbsphinx = "^0.8.8" diff --git a/setup.py b/setup.py index 911df596c..9205d4abd 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +from setuptools import setup import sys from setuptools import setup, find_namespace_packages From 45dd17995b02652a50d0e874ff4bd157866bcc7d Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 11:27:16 +0100 Subject: [PATCH 06/10] Fixed syntax --- .readthedocs.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9f6a21429..d0f657c07 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,6 +9,11 @@ build: os: ubuntu-22.04 tools: python: "3.10" + # Install the project in editable mode. Avoid needing a requirements.txt + jobs: + pre_build: + steps: + - pip install -e ".[dev-docs]" # Build documentation in the "docs/" directory with Sphinx sphinx: @@ -25,8 +30,3 @@ sphinx: # install: # - requirements: docs/requirements-docs.txt -# Install the project in editable mode. Avoid needing a requirements.txt -jobs: - pre-build: - steps: - - pip install -e ".[dev-docs]" From 6a7f5513e425f70fcdd2c0facc5daf3731172738 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 11:28:26 +0100 Subject: [PATCH 07/10] Fixing more syntax --- .readthedocs.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index d0f657c07..8e81c6626 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,8 +12,7 @@ build: # Install the project in editable mode. Avoid needing a requirements.txt jobs: pre_build: - steps: - - pip install -e ".[dev-docs]" + - pip install -e ".[dev-docs]" # Build documentation in the "docs/" directory with Sphinx sphinx: From 8cabb3f4ca67f89df106a6b42eb9888481c24925 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Wed, 14 Feb 2024 19:15:02 +0100 Subject: [PATCH 08/10] Got autosummary working with a custom template. There are numerous warnings during the build due to syntax etc but at least the docs render locally. Added several `__init__.py` files to enable autosummary to recurse into submodules. Also some modules were run upon import which broke the build so added `if __name__ == "__main__"` where necessary. For some reason, the widgets submodule actually causes a segfault during the sphinx build which is amazing. I removed the blank `__init__.py` to avoid sphinx recursing into that submodule so the docs can build at least. --- .gitignore | 8 +- .readthedocs.yaml | 15 +- docs/Makefile | 5 +- docs/conf.py | 22 +- docs/design_concepts/config_options.md | 153 ------- docs/design_concepts/config_options.rst | 194 +++++++++ docs/design_concepts/configuration.md | 302 -------------- docs/design_concepts/configuration.rst | 409 +++++++++++++++++++ docs/design_concepts/connectors.md | 23 -- docs/design_concepts/connectors.rst | 20 + docs/design_concepts/hardware_interface.md | 134 ------ docs/design_concepts/hardware_interface.rst | 159 +++++++ docs/design_concepts/logging.md | 137 ------- docs/design_concepts/logging.rst | 177 ++++++++ docs/design_concepts/measurement_modules.md | 286 ------------- docs/design_concepts/measurement_modules.rst | 361 ++++++++++++++++ docs/design_concepts/remote_modules.md | 47 --- docs/design_concepts/remote_modules.rst | 63 +++ docs/design_concepts/status_variables.md | 128 ------ docs/design_concepts/status_variables.rst | 150 +++++++ docs/index.md | 33 -- docs/index.rst | 23 ++ docs/requirements-docs.txt | 20 - docs/templates/custom-class-template.rst | 36 ++ docs/templates/custom-module-template.rst | 70 ++++ pyproject.toml | 14 +- src/qudi/core/__main__.py | 64 +-- src/qudi/gui/taskrunner/__init__.py | 0 src/qudi/logic/__init__.py | 0 src/qudi/tasks/__init__.py | 0 src/qudi/tools/config_editor/__main__.py | 5 +- src/qudi/util/__init__.py | 0 src/qudi/util/fit_models/__init__.py | 0 src/qudi/util/parameters.py | 20 +- 34 files changed, 1746 insertions(+), 1332 deletions(-) delete mode 100644 docs/design_concepts/config_options.md create mode 100644 docs/design_concepts/config_options.rst delete mode 100644 docs/design_concepts/configuration.md create mode 100644 docs/design_concepts/configuration.rst delete mode 100644 docs/design_concepts/connectors.md create mode 100644 docs/design_concepts/connectors.rst delete mode 100644 docs/design_concepts/hardware_interface.md create mode 100644 docs/design_concepts/hardware_interface.rst delete mode 100644 docs/design_concepts/logging.md create mode 100644 docs/design_concepts/logging.rst delete mode 100644 docs/design_concepts/measurement_modules.md create mode 100644 docs/design_concepts/measurement_modules.rst delete mode 100644 docs/design_concepts/remote_modules.md create mode 100644 docs/design_concepts/remote_modules.rst delete mode 100644 docs/design_concepts/status_variables.md create mode 100644 docs/design_concepts/status_variables.rst delete mode 100644 docs/index.md create mode 100644 docs/index.rst delete mode 100644 docs/requirements-docs.txt create mode 100644 docs/templates/custom-class-template.rst create mode 100644 docs/templates/custom-module-template.rst create mode 100644 src/qudi/gui/taskrunner/__init__.py create mode 100644 src/qudi/logic/__init__.py create mode 100644 src/qudi/tasks/__init__.py create mode 100644 src/qudi/util/__init__.py create mode 100644 src/qudi/util/fit_models/__init__.py diff --git a/.gitignore b/.gitignore index cd57fb1f4..16f21e539 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ share/python-wheels/ .installed.cfg *.egg -<<<<<<< HEAD # log files *.log *.log.* @@ -71,8 +70,6 @@ share/python-wheels/ .installed.cfg *.egg -======= ->>>>>>> 26647177 (Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout.) # Installer logs pip-log.txt pip-delete-this-directory.txt @@ -95,10 +92,15 @@ cover/ # Sphinx documentation docs/_build/ <<<<<<< HEAD +<<<<<<< HEAD docs/_autosummary/ docs/_static/ ======= >>>>>>> 26647177 (Added rtd config and edited files for myst syntax warnings. RTD site should work now, need to improve formatting and layout.) +======= +docs/_autosummary/ +docs/_static/ +>>>>>>> 25ee7046 (Got autosummary working with a custom template. There are numerous warnings during the build due to syntax etc but at least the docs render locally. Added several `__init__.py` files to enable autosummary to recurse into submodules. Also some modules were run upon import which broke the build so added `if __name__ == "__main__"` where necessary. For some reason, the widgets submodule actually causes a segfault during the sphinx build which is amazing. I removed the blank `__init__.py` to avoid sphinx recursing into that submodule so the docs can build at least.) # Jupyter Notebook .ipynb_checkpoints diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8e81c6626..f84caffb6 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,15 +1,14 @@ # Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -# Required version: 2 -# Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: python: "3.10" - # Install the project in editable mode. Avoid needing a requirements.txt + # Install the project in editable mode using pyproject.toml, + # avoids needing a requirements.txt jobs: pre_build: - pip install -e ".[dev-docs]" @@ -17,15 +16,5 @@ build: # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/conf.py - # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs - # builder: "dirhtml" # Fail on all warnings to avoid broken references # fail_on_warning: true - -# Optional but recommended, declare the Python requirements required -# to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -# python: -# install: -# - requirements: docs/requirements-docs.txt - diff --git a/docs/Makefile b/docs/Makefile index d4bb2cbb9..9771e9d7e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. -SPHINXOPTS ?= +SPHINXOPTS ?= --jobs=auto SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build @@ -18,3 +18,6 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + rm -rf _build _autosummary diff --git a/docs/conf.py b/docs/conf.py index 568d363c8..24b4ca367 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,11 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import sys + +sys.path.insert(0, os.path.abspath('../src/qudi/')) + project = 'qudi-core' copyright = '2024, Ulm IQO' author = 'Ulm IQO' @@ -16,15 +21,16 @@ # 'IPython.sphinxext.ipython_console_highlighting', # 'nbsphinx', extensions = [ + 'IPython.sphinxext.ipython_directive', 'IPython.sphinxext.ipython_directive', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'numpydoc', - 'myst_parser', + 'sphinx_rtd_dark_mode', ] -templates_path = ['_templates'] +templates_path = ['templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] autosummary_generate = True @@ -32,13 +38,15 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'pydata_sphinx_theme' +# html_theme = 'pydata_sphinx_theme' # html_theme = 'sphinx_book_theme' -html_static_path = ['_static'] +html_theme = 'sphinx_rtd_theme' +html_theme_options = { + "navigation_with_keys": False, # See https://github.com/pydata/pydata-sphinx-theme/issues/1492 +} +default_dark_mode = False # For sphinx_rtd_dark_mode. Dark mode needs tweaking so not defaulting to it yet. +html_static_path = [] # Normally defaults to '_static' but we don't have any static files. numpydoc_show_class_members = False numpydoc_show_inherited_class_members = False numpydoc_class_members_toctree = False - -myst_heading_anchors = 4 - diff --git a/docs/design_concepts/config_options.md b/docs/design_concepts/config_options.md deleted file mode 100644 index b17b8bc93..000000000 --- a/docs/design_concepts/config_options.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Configuration Options - -When working with [measurement modules](measurement_modules.md) (hardware/logic/GUI) you may want -to give the user the opportunity to statically configure certain aspects of the measurement module. - -[Static configuration](configuration.md) in qudi is generally handled via a YAML configuration file -that is read and parsed during the application startup process. -All [measurement modules](measurement_modules.md) included in the qudi session are declared in this -file (among other things). But apart from the mandatory properties you can declare any number of -additional properties inside the `options` property for each measurement module. -Please refer to the [qudi configuration documentation](configuration.md) for more details on config -files. - -A measurement module constant that is automatically initialized from the qudi configuration is -called a "configuration option" or "config option". - -> **⚠ WARNING:** -> -> Config options are initialized only ONCE during instantiation of a measurement module and NOT -> every time the module is activated. -> So it is good practice to keep config option data members constant during runtime. - -## Usage -In order to simplify and automate the process of initializing these data members and prevent each -measurement module to implement their own solution, qudi provides the meta object -`qudi.core.configoption.ConfigOption`. - -When implementing a [measurement module](measurement_modules.md) (hardware/logic/GUI) you can -simply instantiate `ConfigOption` class variables. These meta objects will be transformed into -regular variable members of your measurement module instances and can be used like any normal -instance variable in Python: -```python -from qudi.core.configoption import ConfigOption -from qudi.core.module import LogicBase - -class MyExampleLogic(LogicBase): - """ Module description goes here """ - - _my_config_option = ConfigOption(name='my_config_option', - default='Not configured', - missing='warn') - - ... - - def print_my_config_option(self): - print(self._my_config_option) - - ... -``` -The corresponding module section in the config file would look like: -```yaml -global: - ... - -gui: - ... - -hardware: - ... - -logic: - example_logic_identifier_name: - module.Class: my_example_logic.MyExampleLogic - options: - my_config_option: 'I am a string from the qudi configuration' - connect: - ... - ... -``` - -### name -Please note here that the variable name in the measurement module definition is `_my_config_option`, -while the name given in the config file is `my_config_option` (without underscore). This is -possible because of the optional `name` argument of `ConfigOption`. This argument specifies the -field name of the config option in the qudi configuration and can be any YAML-compatible string as -long as it is unique within a measurement module. - -### default -This example is also defining an optional `default` value for the config option. If you specify a -default value, this config option is considered optional, i.e. if you do not provide the config -option via qudi configuration, it will be initialized to this default value instead. -Non-optional config options (omitting the `default` argument) will cause the measurement module to -raise an exception during initialization if the corresponding field is not specified in the qudi -configuration. - -### missing -The optional `missing` argument can be used to define the behaviour in case the config option is -missing from the configuration and has a default value. Ignored for non-optional config options. -Possible argument values are: - -| value | effect | -| --------------- | ----------------------------------------------------------------------------------------- | -| `'nothing'` | Silently use the default value. | -| `'info'` | Use default value but also logs an info message about the missing config option. | -| `'warn'` | Use default value but also logs a warning about the missing config option. | -| `'error'` | Fail to initialize the module with an exception. Same as for non-optional config options. | - -### checker -If you want to establish sanity checking for your config option at module initialization, you can -provide a static function to the optional `checker` argument of `ConfigOption`. -This function should accept a single argument (the configured value coming from the YAML loader) -and return a boolean indicating if the check has passed (`True`) or not. - -### constructor -Since config options must be provided via YAML format you are limited in what data types can be -configured. The qudi YAML loader currently supports any native Python builtin type and numpy arrays. - -If your config option should be of any other type, you need to provide a `constructor` function to -the `ConfigOption` meta object. -This function must accept the simple YAML data and return converted data that is then used to -initialize the module data member. -You can provide a callable as `constructor` argument to `ConfigOption` or you can register a -callable member of your measurement module as such via decorator, e.g.: -```python -from qudi.core.configoption import ConfigOption -from qudi.core.module import LogicBase - -class FancyDataType: - def __init__(self, a, b): - self.a = a - self.b = b - - -class MyExampleLogic(LogicBase): - """ Module description goes here """ - - _my_config_option = ConfigOption(name='my_config_option') - _my_other_config_option = ConfigOption(name='my_other_config_option', - constructor=lambda yaml_data: FancyDataType(*yaml_data)) - - ... - - @_my_config_option.constructor - def my_config_option_constructor(self, yaml_data): - return FancyDataType(*yaml_data) - - ... -``` -Since the `constructor` function is usually static (as the example above also shows), you could -combine that with the `@staticmethod` decorator. But this is not necessary and just good style. - ---- - -[index](../index.md) diff --git a/docs/design_concepts/config_options.rst b/docs/design_concepts/config_options.rst new file mode 100644 index 000000000..6201a619f --- /dev/null +++ b/docs/design_concepts/config_options.rst @@ -0,0 +1,194 @@ +`index <../index.md>`__ + +-------------- + +Configuration Options +===================== + +When working with `measurement modules `__ +(hardware/logic/GUI) you may want to give the user the opportunity to +statically configure certain aspects of the measurement module. + +| `Static configuration `__ in qudi is generally + handled via a YAML configuration file that is read and parsed during + the application startup process. +| All `measurement modules `__ included in the + qudi session are declared in this file (among other things). But apart + from the mandatory properties you can declare any number of additional + properties inside the ``options`` property for each measurement + module. +| Please refer to the `qudi configuration + documentation `__ for more details on config files. + +A measurement module constant that is automatically initialized from the +qudi configuration is called a “configuration option” or “config +option”. + + **⚠ WARNING:** + + | Config options are initialized only ONCE during instantiation of a + measurement module and NOT every time the module is activated. + | So it is good practice to keep config option data members constant + during runtime. + +Usage +----- + +In order to simplify and automate the process of initializing these data +members and prevent each measurement module to implement their own +solution, qudi provides the meta object +``qudi.core.configoption.ConfigOption``. + +When implementing a `measurement module `__ +(hardware/logic/GUI) you can simply instantiate ``ConfigOption`` class +variables. These meta objects will be transformed into regular variable +members of your measurement module instances and can be used like any +normal instance variable in Python: + +.. code:: python + + from qudi.core.configoption import ConfigOption + from qudi.core.module import LogicBase + + class MyExampleLogic(LogicBase): + """ Module description goes here """ + + _my_config_option = ConfigOption(name='my_config_option', + default='Not configured', + missing='warn') + + ... + + def print_my_config_option(self): + print(self._my_config_option) + + ... + +The corresponding module section in the config file would look like: + +.. code:: yaml + + global: + ... + + gui: + ... + + hardware: + ... + + logic: + example_logic_identifier_name: + module.Class: my_example_logic.MyExampleLogic + options: + my_config_option: 'I am a string from the qudi configuration' + connect: + ... + ... + +name +~~~~ + +Please note here that the variable name in the measurement module +definition is ``_my_config_option``, while the name given in the config +file is ``my_config_option`` (without underscore). This is possible +because of the optional ``name`` argument of ``ConfigOption``. This +argument specifies the field name of the config option in the qudi +configuration and can be any YAML-compatible string as long as it is +unique within a measurement module. + +default +~~~~~~~ + +| This example is also defining an optional ``default`` value for the + config option. If you specify a default value, this config option is + considered optional, i.e. if you do not provide the config option via + qudi configuration, it will be initialized to this default value + instead. +| Non-optional config options (omitting the ``default`` argument) will + cause the measurement module to raise an exception during + initialization if the corresponding field is not specified in the qudi + configuration. + +missing +~~~~~~~ + +| The optional ``missing`` argument can be used to define the behaviour + in case the config option is missing from the configuration and has a + default value. Ignored for non-optional config options. +| Possible argument values are: + ++---------+------------------------------------------------------------+ +| value | effect | ++=========+============================================================+ +| ``'not | Silently use the default value. | +| hing'`` | | ++---------+------------------------------------------------------------+ +| ``' | Use default value but also logs an info message about the | +| info'`` | missing config option. | ++---------+------------------------------------------------------------+ +| ``' | Use default value but also logs a warning about the | +| warn'`` | missing config option. | ++---------+------------------------------------------------------------+ +| ``'e | Fail to initialize the module with an exception. Same as | +| rror'`` | for non-optional config options. | ++---------+------------------------------------------------------------+ + +checker +~~~~~~~ + +| If you want to establish sanity checking for your config option at + module initialization, you can provide a static function to the + optional ``checker`` argument of ``ConfigOption``. +| This function should accept a single argument (the configured value + coming from the YAML loader) and return a boolean indicating if the + check has passed (``True``) or not. + +constructor +~~~~~~~~~~~ + +Since config options must be provided via YAML format you are limited in +what data types can be configured. The qudi YAML loader currently +supports any native Python builtin type and numpy arrays. + +| If your config option should be of any other type, you need to provide + a ``constructor`` function to the ``ConfigOption`` meta object. +| This function must accept the simple YAML data and return converted + data that is then used to initialize the module data member. +| You can provide a callable as ``constructor`` argument to + ``ConfigOption`` or you can register a callable member of your + measurement module as such via decorator, e.g.: + +.. code:: python + + from qudi.core.configoption import ConfigOption + from qudi.core.module import LogicBase + + class FancyDataType: + def __init__(self, a, b): + self.a = a + self.b = b + + + class MyExampleLogic(LogicBase): + """ Module description goes here """ + + _my_config_option = ConfigOption(name='my_config_option') + _my_other_config_option = ConfigOption(name='my_other_config_option', + constructor=lambda yaml_data: FancyDataType(*yaml_data)) + + ... + + @_my_config_option.constructor + def my_config_option_constructor(self, yaml_data): + return FancyDataType(*yaml_data) + + ... + +Since the ``constructor`` function is usually static (as the example +above also shows), you could combine that with the ``@staticmethod`` +decorator. But this is not necessary and just good style. + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/configuration.md b/docs/design_concepts/configuration.md deleted file mode 100644 index f18326e79..000000000 --- a/docs/design_concepts/configuration.md +++ /dev/null @@ -1,302 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Configuration - -Since qudi is a very modular and versatile application, it is only natural to have means of -customizing a qudi session. -This customization or configuration is done once at startup of each qudi session by parsing a -configuration text file. It contains global constants/settings as well as the custom naming -and setting for each qudi module to be used and instructions on how to interconnect them. What -qudi modules you have available during a qudi session is therefore defined by this configuration. - -Because the configuration file is just parsed once during startup, the qudi configuration should be -considered static and constant during a qudi session and is not to be altered during runtime. - -## File Format -The configuration file is a text file in [YAML](https://en.wikipedia.org/wiki/YAML) format with -filename extension `.cfg`. -Qudi implements a relaxed syntax for YAML - straying a bit from the official specifications - in -order to allow more verbose Python input (e.g. `True` in addition to `true`). - -Non-mandatory properties with default values are automatically inserted upon file parsing. - -The content is structured as a nested mapping with string keys and is divided into 2 main parts: - -### Global Section -This section contains all settings that are not tied to a specific qudi module. It contains some -predefined properties with default values but currently none of them is required to be provided by -the config file. It is also allowed to add additional properties to this section by simply -incorporating them in the config file. - -The default content of the `global` section looks as follows: -```yaml -global: - startup_modules: [] - remote_modules_server: null - namespace_server_port: 18861 - force_remote_calls_by_value: True - hide_manager_window: False - stylesheet: 'qdark.qss' - default_data_dir: null - daily_data_dirs: True - extension_paths: [] -``` -Please note that the above content will be created even if leave out the `global` section entirely. - -Let's give a rundown on each default property: - -#### startup_modules -This is an optional list of configured module (gui/logic/hardware) name strings. More detail on the -module names can be found further down in the ["modules sections"](#modules-sections) section. - -Each module in that list will be automatically loaded and activated after qudi startup. - -Please note that it is sufficient to just state the highest-level module you want to automatically -activate and _**not**_ all dependencies as well. E.g. if you want to automatically load a -certain measurement toolchain at qudi startup, it is enough to just give the name of the -corresponding GUI module. - -Example: -```yaml -global: - startup_modules: - - 'my_gui_module' - -gui: - my_gui_module: - ... -``` - -#### remote_modules_server -If you want to expose qudi modules running on the local machine to networked remote qudi instances, -you need to specify the server settings with this property. -By default this property will be `null`, meaning the remote module server is disabled. -Please note that you are still able to connect to remote running qudi modules even if the local -remote modules server is disabled (as long as the remote qudi instance has a server running of -course). - -> **⚠ WARNING:** -> -> It is highly recommended to use closed local area networks to communicate between different qudi -> instances due to security reasons. -> The connection of qudi modules using SSL is still very experimental and secure connections can not -> be guaranteed. -> -> If you want to use SSL encryption, you need to generate client and server certificates with an -> external tool. - -In case you want to serve local modules to other qudi instances, the server configuration is a -mapping with the following properties: - -| property | type | description | -|:-----------|:-----------------|-------------------------------------------------------------------------------------------------------------------------------------| -| `address` | `str` | Host name of the server to be reached with.
If you want to serve only local qudi instances, you can set this to `'localhost'`. | -| `port` | `int` | Port number to bind the server to. | -| `certfile` | `Optional[str]` | Path to the SSL certificate file to use for connection encryption. Unsecured if omitted. | -| `keyfile` | `Optional[str]` | Path to the SSL key file to use for connection encryption. Unsecured if omitted. | - -Example: -```yaml -global: - remote_modules_server: - address: '192.168.1.100' - port: 12345 - certfile: '/path/to/certfile.cert' # omit for unsecured - keyfile: '/path/to/keyfile.key' # omit for unsecured -``` - -#### namespace_server_port -Qudi namespace server port number (`int`) to bind to. -The qudi namespace server is similar to the remote modules server except it always runs only on -`localhost` and is unencrypted. It serves as interface to qudi for local running IPython kernels -(Jupyter notebooks, qudi console, etc.). - -#### force_remote_calls_by_value -Boolean flag to enable (`True`) or disable (`False`) all arguments passed to qudi module APIs from -remote (jupyter notebook, qudi console, remote modules) to be wrapped and passed "by value" -(serialized and de-serialized) instead of "by reference". This is avoiding a lot of inconveniences -with using `numpy` in remote clients. - -By default this feature is enabled but if you know what you are doing you can unset this flag. - -#### hide_manager_window -Optional boolean flag to hide the qudi manager window upon startup. This can be useful in tandem -with the `startup_modules` property to restrict GUI access. - -#### stylesheet -Full path or filename (`str`) to a Qt compatible QSS stylesheet to be used for this qudi session. -If only a filename is given, qudi assumes to find this file in `qudi.artwork.styles`. - -#### default_data_dir -If given an absolute directory path (`str`), it overwrites the default root directory for qudi to -store measurement data in (assuming used data storage is file system based). - -By default qudi is using `/qudi/Data/` as data root directory. - -Example: -```yaml -global: - default_data_dir: 'C:\\Data\\' -``` - -#### daily_data_dirs -Boolean flag used by some file based data storage methods to determine if daily data -sub-directories should be automatically created. - -#### extension_paths -List of absolute paths (`str`) to be inserted to the beginning of `sys.path` at runtime in order to -overwrite module import path resolution with custom locations. - -> **⚠ WARNING:** -> -> This feature is deprecated and will be removed in future releases of `qudi-core` because it is -> unpredictable and causes more harm than it does good. -> -> Since `qudi-core v1.0.0` `qudi` is a proper namespace package that can be extended by installing -> more modules into it via e.g. `pip`. - -### Modules Sections -The second part of the config file is actually divided into 3 properties with the same structure -configuring `gui`, `logic` and `hardware` modules to be available in the qudi session. - -Each qudi module configured must be given a name which must be unique throughout the configuration. -This name string will be the property name under the respective qudi module category -(`gui`, `logic`, `hardware`) containing the module-specific configuration. -Module names must not start with a number and contain only ASCII word characters (standard letters -a-z, number digits and underscores). - -The individual module configuration must follow one of two possible structures: - -#### Local Module -Local modules are modules to be run natively in the qudi instance configured by this config file. -This is the "normal" way to configure a module and each module used in a network of qudi instances -must be configured like this in exactly one qudi instance. - -An example for a minimum local logic module configuration looks like this: -```yaml -logic: - my_module: # unique custom name for this module - module.Class: 'my_module.MyModuleClass' -``` -In this example the respective `qudi.core.module.LogicBase` subclass is called `MyModuleClass` and -can be imported from `qudi.logic.my_module`. - -If you are running a remote modules server to make a qudi module available to a remote qudi -instance, you need to flag each module that should be accessible from remote. -To do so you need to set the module config property `allow_remote` to `True` (it is `False` by default): -```yaml -logic: - my_module: # unique custom name for this module - module.Class: 'my_module.MyModuleClass' - allow_remote: True -``` - -In order to interface different modules with each other, qudi modules are employing a meta-object -called a `Connector` ([more details here](connectors.md)). -If the logic module in our example needs to be connected to other modules (logic or hardware), you -have to specify this in the module configuration as well. The modules to connect to are addressed -by their module names given in the same config: -```yaml -logic: - my_module: - module.Class: 'my_module.MyModuleClass' - connect: - my_connector_name: 'my_other_module' -``` - -Now in order to configure static variables in the module configuration qudi modules use -`ConfigOption` meta-objects ([more details here](config_options.md)). -If the logic module in our example needs to have options configured, you have to specify this in -the module configuration as well. The name of the config option is determined by the respective -`ConfigOption` meta attribute in the qudi module class: -```yaml -logic: - my_module: - module.Class: 'my_module.MyModuleClass' - connect: - my_connector_name: 'my_other_module' - options: - my_first_config_option: 'hello world' - my_second_config_option: - - 42 - - 123.456 - - ['a', 'b', 'c'] -``` - -#### Remote Module -A remote module is declared in its respective local qudi instance as local module of course. But if -you are configuring a qudi instance to connect to a module running in another remote qudi instance, -you need to specify that properly. -When naming a remote qudi module you can do so without regarding the original module name in its -local qudi instance configuration. - -Contrary to a local module you can not configure options or connections in remote modules (this is -done in their respective local qudi config). The only thing you have to configure is the network -connection details (address and port), the native module name on the remote qudi instance and, in -case the connection is SSL secured, also key and certificate file paths: - -```yaml -hardware: - my_remote_module: - native_module_name: 'module_name_on_remote_host' - address: '192.168.1.100' - port: 12345 - certfile: '/path/to/certfile.cert' # omit for unsecured - keyfile: '/path/to/keyfile.key' # omit for unsecured -``` - -As you can probably see, the config looks very much like the `remote_module_server` global config -entry [above](#remote_modules_server). In fact the `address` and `port` items must mirror the -`remote_module_server` config on the remote qudi instance to connect to. - - -## Validation -Generally you should be able to express any property in the config as one of these types: -- scalar (`float`, `int`, `str`, `bool`, `null`) -- sequence (`list`) -- mapping (`dict`) - -Of course you can also nest sequences and mappings. - -Validation, type checking and default value insertion is performed via -[JSON Schema](https://json-schema.org/) -([Draft-07](https://json-schema.org/draft-07/json-schema-release-notes.html)) each time a config -file is loaded or dumped. -The schema to be used can be found in -[`qudi.core.config.schema`](https://github.com/Ulm-IQO/qudi-core/blob/config-refactoring/src/qudi/core/config/schema.py). - - -## Graphical Configuration Editor - -> **⚠ WARNING:** -> -> The graphical configuration editor is still in an early development phase and may not be -> functional yet. -> -> When starting the editor you will probably encounter a long series of warnings and errors coming -> from qudi module imports. -> This is expected behaviour and should not influence the functionality -> of the editor. In the future these errors will be properly handled behind the scenes. - -You can start a standalone graphical qudi configuration editor currently in two different ways: - -1. By running `qudi-config-editor` inside your qudi Python environment: - ```console - (qudi-venv) C:\> qudi-config-editor - ``` -2. By executing the runnable qudi module `qudi.tools.config_editor` inside your qudi Python -environment: - ```console - (qudi-venv) C:\Software\qudi-core\src\qudi\tools\> python -m config_editor - ``` - ---- - -[index](../index.md) diff --git a/docs/design_concepts/configuration.rst b/docs/design_concepts/configuration.rst new file mode 100644 index 000000000..a2d2b089e --- /dev/null +++ b/docs/design_concepts/configuration.rst @@ -0,0 +1,409 @@ +`index <../index.md>`__ + +-------------- + +Configuration +============= + +| Since qudi is a very modular and versatile application, it is only + natural to have means of customizing a qudi session. +| This customization or configuration is done once at startup of each + qudi session by parsing a configuration text file. It contains global + constants/settings as well as the custom naming and setting for each + qudi module to be used and instructions on how to interconnect them. + What qudi modules you have available during a qudi session is + therefore defined by this configuration. + +Because the configuration file is just parsed once during startup, the +qudi configuration should be considered static and constant during a +qudi session and is not to be altered during runtime. + +File Format +----------- + +| The configuration file is a text file in + `YAML `__ format with filename + extension ``.cfg``. +| Qudi implements a relaxed syntax for YAML - straying a bit from the + official specifications - in order to allow more verbose Python input + (e.g. ``True`` in addition to ``true``). + +Non-mandatory properties with default values are automatically inserted +upon file parsing. + +The content is structured as a nested mapping with string keys and is +divided into 2 main parts: + +Global Section +~~~~~~~~~~~~~~ + +This section contains all settings that are not tied to a specific qudi +module. It contains some predefined properties with default values but +currently none of them is required to be provided by the config file. It +is also allowed to add additional properties to this section by simply +incorporating them in the config file. + +The default content of the ``global`` section looks as follows: + +.. code:: yaml + + global: + startup_modules: [] + remote_modules_server: null + namespace_server_port: 18861 + force_remote_calls_by_value: True + hide_manager_window: False + stylesheet: 'qdark.qss' + default_data_dir: null + daily_data_dirs: True + extension_paths: [] + +Please note that the above content will be created even if leave out the +``global`` section entirely. + +Let’s give a rundown on each default property: + +startup_modules +^^^^^^^^^^^^^^^ + +This is an optional list of configured module (gui/logic/hardware) name +strings. More detail on the module names can be found further down in +the `“modules sections” <#modules-sections>`__ section. + +Each module in that list will be automatically loaded and activated +after qudi startup. + +Please note that it is sufficient to just state the highest-level module +you want to automatically activate and **not** all dependencies as well. +E.g. if you want to automatically load a certain measurement toolchain +at qudi startup, it is enough to just give the name of the corresponding +GUI module. + +Example: + +.. code:: yaml + + global: + startup_modules: + - 'my_gui_module' + + gui: + my_gui_module: + ... + +remote_modules_server +^^^^^^^^^^^^^^^^^^^^^ + +| If you want to expose qudi modules running on the local machine to + networked remote qudi instances, you need to specify the server + settings with this property. By default this property will be + ``null``, meaning the remote module server is disabled. +| Please note that you are still able to connect to remote running qudi + modules even if the local remote modules server is disabled (as long + as the remote qudi instance has a server running of course). + + **⚠ WARNING:** + + | It is highly recommended to use closed local area networks to + communicate between different qudi instances due to security + reasons. + | The connection of qudi modules using SSL is still very experimental + and secure connections can not be guaranteed. + + If you want to use SSL encryption, you need to generate client and + server certificates with an external tool. + +In case you want to serve local modules to other qudi instances, the +server configuration is a mapping with the following properties: + ++----+------+---------------------------------------------------------+ +| pr | type | description | +| op | | | +| er | | | +| ty | | | ++====+======+=========================================================+ +| ` | ``s | Host name of the server to be reached with. If you want | +| `a | tr`` | to serve only local qudi instances, you can set this to | +| dd | | ``'localhost'``. | +| re | | | +| ss | | | +| `` | | | ++----+------+---------------------------------------------------------+ +| `` | ``i | Port number to bind the server to. | +| po | nt`` | | +| rt | | | +| `` | | | ++----+------+---------------------------------------------------------+ +| `` | ` | Path to the SSL certificate file to use for connection | +| ce | `Opt | encryption. Unsecured if omitted. | +| rt | iona | | +| fi | l[st | | +| le | r]`` | | +| `` | | | ++----+------+---------------------------------------------------------+ +| ` | ` | Path to the SSL key file to use for connection | +| `k | `Opt | encryption. Unsecured if omitted. | +| ey | iona | | +| fi | l[st | | +| le | r]`` | | +| `` | | | ++----+------+---------------------------------------------------------+ + +Example: + +.. code:: yaml + + global: + remote_modules_server: + address: '192.168.1.100' + port: 12345 + certfile: '/path/to/certfile.cert' # omit for unsecured + keyfile: '/path/to/keyfile.key' # omit for unsecured + +namespace_server_port +^^^^^^^^^^^^^^^^^^^^^ + +| Qudi namespace server port number (``int``) to bind to. +| The qudi namespace server is similar to the remote modules server + except it always runs only on ``localhost`` and is unencrypted. It + serves as interface to qudi for local running IPython kernels (Jupyter + notebooks, qudi console, etc.). + +force_remote_calls_by_value +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Boolean flag to enable (``True``) or disable (``False``) all arguments +passed to qudi module APIs from remote (jupyter notebook, qudi console, +remote modules) to be wrapped and passed “by value” (serialized and +de-serialized) instead of “by reference”. This is avoiding a lot of +inconveniences with using ``numpy`` in remote clients. + +By default this feature is enabled but if you know what you are doing +you can unset this flag. + +hide_manager_window +^^^^^^^^^^^^^^^^^^^ + +Optional boolean flag to hide the qudi manager window upon startup. This +can be useful in tandem with the ``startup_modules`` property to +restrict GUI access. + +stylesheet +^^^^^^^^^^ + +Full path or filename (``str``) to a Qt compatible QSS stylesheet to be +used for this qudi session. If only a filename is given, qudi assumes to +find this file in ``qudi.artwork.styles``. + +default_data_dir +^^^^^^^^^^^^^^^^ + +If given an absolute directory path (``str``), it overwrites the default +root directory for qudi to store measurement data in (assuming used data +storage is file system based). + +By default qudi is using ``/qudi/Data/`` as data root +directory. + +Example: + +.. code:: yaml + + global: + default_data_dir: 'C:\\Data\\' + +daily_data_dirs +^^^^^^^^^^^^^^^ + +Boolean flag used by some file based data storage methods to determine +if daily data sub-directories should be automatically created. + +extension_paths +^^^^^^^^^^^^^^^ + +List of absolute paths (``str``) to be inserted to the beginning of +``sys.path`` at runtime in order to overwrite module import path +resolution with custom locations. + + **⚠ WARNING:** + + This feature is deprecated and will be removed in future releases of + ``qudi-core`` because it is unpredictable and causes more harm than + it does good. + + Since ``qudi-core v1.0.0`` ``qudi`` is a proper namespace package + that can be extended by installing more modules into it via + e.g. ``pip``. + +Modules Sections +~~~~~~~~~~~~~~~~ + +The second part of the config file is actually divided into 3 properties +with the same structure configuring ``gui``, ``logic`` and ``hardware`` +modules to be available in the qudi session. + +| Each qudi module configured must be given a name which must be unique + throughout the configuration. This name string will be the property + name under the respective qudi module category (``gui``, ``logic``, + ``hardware``) containing the module-specific configuration. +| Module names must not start with a number and contain only ASCII word + characters (standard letters a-z, number digits and underscores). + +The individual module configuration must follow one of two possible +structures: + +Local Module +^^^^^^^^^^^^ + +Local modules are modules to be run natively in the qudi instance +configured by this config file. This is the “normal” way to configure a +module and each module used in a network of qudi instances must be +configured like this in exactly one qudi instance. + +An example for a minimum local logic module configuration looks like +this: + +.. code:: yaml + + logic: + my_module: # unique custom name for this module + module.Class: 'my_module.MyModuleClass' + +In this example the respective ``qudi.core.module.LogicBase`` subclass +is called ``MyModuleClass`` and can be imported from +``qudi.logic.my_module``. + +| If you are running a remote modules server to make a qudi module + available to a remote qudi instance, you need to flag each module that + should be accessible from remote. +| To do so you need to set the module config property ``allow_remote`` + to ``True`` (it is ``False`` by default): + +.. code:: yaml + + logic: + my_module: # unique custom name for this module + module.Class: 'my_module.MyModuleClass' + allow_remote: True + +| In order to interface different modules with each other, qudi modules + are employing a meta-object called a ``Connector`` (`more details + here `__). +| If the logic module in our example needs to be connected to other + modules (logic or hardware), you have to specify this in the module + configuration as well. The modules to connect to are addressed by + their module names given in the same config: + +.. code:: yaml + + logic: + my_module: + module.Class: 'my_module.MyModuleClass' + connect: + my_connector_name: 'my_other_module' + +| Now in order to configure static variables in the module configuration + qudi modules use ``ConfigOption`` meta-objects (`more details + here `__). +| If the logic module in our example needs to have options configured, + you have to specify this in the module configuration as well. The name + of the config option is determined by the respective ``ConfigOption`` + meta attribute in the qudi module class: + +.. code:: yaml + + logic: + my_module: + module.Class: 'my_module.MyModuleClass' + connect: + my_connector_name: 'my_other_module' + options: + my_first_config_option: 'hello world' + my_second_config_option: + - 42 + - 123.456 + - ['a', 'b', 'c'] + +Remote Module +^^^^^^^^^^^^^ + +| A remote module is declared in its respective local qudi instance as + local module of course. But if you are configuring a qudi instance to + connect to a module running in another remote qudi instance, you need + to specify that properly. +| When naming a remote qudi module you can do so without regarding the + original module name in its local qudi instance configuration. + +Contrary to a local module you can not configure options or connections +in remote modules (this is done in their respective local qudi config). +The only thing you have to configure is the network connection details +(address and port), the native module name on the remote qudi instance +and, in case the connection is SSL secured, also key and certificate +file paths: + +.. code:: yaml + + hardware: + my_remote_module: + native_module_name: 'module_name_on_remote_host' + address: '192.168.1.100' + port: 12345 + certfile: '/path/to/certfile.cert' # omit for unsecured + keyfile: '/path/to/keyfile.key' # omit for unsecured + +As you can probably see, the config looks very much like the +``remote_module_server`` global config entry +`above <#remote_modules_server>`__. In fact the ``address`` and ``port`` +items must mirror the ``remote_module_server`` config on the remote qudi +instance to connect to. + +Validation +---------- + +Generally you should be able to express any property in the config as +one of these types: - scalar (``float``, ``int``, ``str``, ``bool``, +``null``) - sequence (``list``) - mapping (``dict``) + +Of course you can also nest sequences and mappings. + +| Validation, type checking and default value insertion is performed via + `JSON Schema `__ + (`Draft-07 `__) + each time a config file is loaded or dumped. +| The schema to be used can be found in + ```qudi.core.config.schema`` `__. + +Graphical Configuration Editor +------------------------------ + + **⚠ WARNING:** + + The graphical configuration editor is still in an early development + phase and may not be functional yet. + + | When starting the editor you will probably encounter a long series + of warnings and errors coming from qudi module imports. + | This is expected behaviour and should not influence the + functionality of the editor. In the future these errors will be + properly handled behind the scenes. + +You can start a standalone graphical qudi configuration editor currently +in two different ways: + +1. By running ``qudi-config-editor`` inside your qudi Python + environment: + + .. code:: console + + (qudi-venv) C:\> qudi-config-editor + +2. By executing the runnable qudi module ``qudi.tools.config_editor`` + inside your qudi Python environment: + + .. code:: console + + (qudi-venv) C:\Software\qudi-core\src\qudi\tools\> python -m config_editor + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/connectors.md b/docs/design_concepts/connectors.md deleted file mode 100644 index 7d5436169..000000000 --- a/docs/design_concepts/connectors.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Connectors - -WORK IN PROGRESS - -Calling a connector will return a reference to the connected module instance. More precisely, it -will return a transparent object proxy to said module instance in order to hide the fact that -modules should not own strong references on other modules. But this is an implementation detail. -In case of [interface overloading](../404.md) this proxy will also provide access to the other -modules members via the right interface. - - ---- - -[index](../index.md) diff --git a/docs/design_concepts/connectors.rst b/docs/design_concepts/connectors.rst new file mode 100644 index 000000000..2e19abe28 --- /dev/null +++ b/docs/design_concepts/connectors.rst @@ -0,0 +1,20 @@ +`index <../index.md>`__ + +-------------- + +Connectors +========== + +WORK IN PROGRESS + +| Calling a connector will return a reference to the connected module + instance. More precisely, it will return a transparent object proxy to + said module instance in order to hide the fact that modules should not + own strong references on other modules. But this is an implementation + detail. +| In case of `interface overloading <../404.md>`__ this proxy will also + provide access to the other modules members via the right interface. + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/hardware_interface.md b/docs/design_concepts/hardware_interface.md deleted file mode 100644 index 7cc1c6d2f..000000000 --- a/docs/design_concepts/hardware_interface.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Hardware Interface -When we talk about an "interface" in the context of qudi, mean the hardware interface classes -usually located in `qudi.interface`. - -An interface is an [abstract class](https://en.wikipedia.org/wiki/Abstract_type) and a subclass of -the module `Base` class. It defines a set of abstract methods and properties. -These classes can not be instantiated directly (thus "abstract"). Instead they must be subclassed. - -In order for the subclass to work and not be abstract itself, it needs to implement all the -abstract members of the interface class it inherits. -So every class that inherits the same interface is guaranteed to provide at least the set of -methods and properties defined in the interface. - -Since the interface subclass that implements all the abstract members is also a subclass of `Base`, -this class is then called a [hardware module](../404.md). - - -See also the detailed [qudi modules](https://github.com/Ulm-IQO/qudi-iqo-modules/blob/main/docs/installation_guide.md) documentation if you want to know more about -what defines a qudi module and the respective inheritance trees. - -It's always good to have a look at some example... so here is a simple interface class for you. - -## Example Interface: -```python -from abc import abstractmethod -from qudi.core import Base - -class MySimpleInterface(Base): - """ Interface description and license/copyright header goes here - """ - @abstractmethod - def read_value(self) -> float: - """ Reads a value from the instrument and returns it - """ - pass - - @property - @abstractmethod - def some_setting(self) -> int: - """ Property holding some integer type setting in the instrument - """ - pass - - @some_setting.setter - def some_setting(self, value: int) -> None: - """ Setter for property "some_setting" - """ - pass -``` -> **⚠ WARNING:** -> -> Note the order of decorators when declaring an abstract property. You can not exchange -> `@property` and `@abstractmethod`! - -This interface declares a method `read_value` and a read/write property `some_setting`. -Note also that it inherits `Base`. - -An actual hardware module implementation satisfying this interface could look like below, assuming -you placed the above code in `qudi/interface/my_simple_interface.py`. -## Hardware Implementation: -```python -from qudi.interface.my_simple_interface import MySimpleInterface - -class MySimpleInstrument(MySimpleInterface): - """ Hardware module description and license/copyright header goes here - """ - - def on_activate(self): - """ Initialize module upon activation """ - # Perform any module initialization here, e.g. establish a connection to the instrument etc. - ... - - def on_deactivate(self): - """ Perform module cleanup upon deactivation """ - # Clean up your module and free all resources, e.g. terminate the instrument connection - ... - - def read_value(self) -> float: - """ Reads a value from the instrument and returns it """ - value = ... # Read a value from the instrument - return value - - @property - def some_setting(self) -> int: - """ Property holding some integer type setting in the instrument """ - value = ... # Retrieve the setting value from the instrument - return value - - @some_setting.setter - def some_setting(self, value: int) -> None: - """ Setter for property """ - ... # Perform some sanity checking apply the new setting value to the instrument -``` - -Please note that in addition to the interface members you also needed to implement `on_activate` -and `on_deactivate` which are abstract methods inherited from the `Base` class. - -Implementing the members shown above is the bare minimum the hardware class needs to provide. -Of course, you can always implement additional members (helper methods etc.) to structure your -class in accordance with good programming practices. - -> **⚠ WARNING:** -> -> Other qudi modules that connect to a hardware class through an interface must only ever use/call -> the members declared in the interface. All additional members must only ever be used by the -> hardware class internally. Consequentially, you should consider making them protected or even -> private, i.e. add single `_` or double underscore `__` name prefix, respectively. - -## Why Do All This? -An abstract interface class defines a common set of methods and properties to control and monitor a -certain generalized type of hardware (CW microwave sources, lasers, AWGs, etc.). - -Logic modules that orchestrate instruments via hardware modules usually just define what kind of -interface they want to use and not the specific hardware module, i.e. they require a general type -of hardware without explicitly specifying a specific device model. - -This has the advantage that you can exchange your instruments with various devices of the same -hardware type without changing the code for your experiment procedure (i.e. the logic modules). -As long as all hardware modules use the same interface, you can freely exchange them in the config -while GUI and logic modules will work just the same. -This is also called "hardware abstraction". - ---- - -[index](../index.md) diff --git a/docs/design_concepts/hardware_interface.rst b/docs/design_concepts/hardware_interface.rst new file mode 100644 index 000000000..b21292920 --- /dev/null +++ b/docs/design_concepts/hardware_interface.rst @@ -0,0 +1,159 @@ +`index <../index.md>`__ + +-------------- + +Hardware Interface +================== + +When we talk about an “interface” in the context of qudi, mean the +hardware interface classes usually located in ``qudi.interface``. + +| An interface is an `abstract + class `__ and a subclass + of the module ``Base`` class. It defines a set of abstract methods and + properties. +| These classes can not be instantiated directly (thus “abstract”). + Instead they must be subclassed. + +| In order for the subclass to work and not be abstract itself, it needs + to implement all the abstract members of the interface class it + inherits. +| So every class that inherits the same interface is guaranteed to + provide at least the set of methods and properties defined in the + interface. + +Since the interface subclass that implements all the abstract members is +also a subclass of ``Base``, this class is then called a `hardware +module <../404.md>`__. + +.. raw:: html + + + +See also the detailed `qudi +modules `__ +documentation if you want to know more about what defines a qudi module +and the respective inheritance trees. + +It’s always good to have a look at some example… so here is a simple +interface class for you. + +Example Interface: +------------------ + +.. code:: python + + from abc import abstractmethod + from qudi.core import Base + + class MySimpleInterface(Base): + """ Interface description and license/copyright header goes here + """ + @abstractmethod + def read_value(self) -> float: + """ Reads a value from the instrument and returns it + """ + pass + + @property + @abstractmethod + def some_setting(self) -> int: + """ Property holding some integer type setting in the instrument + """ + pass + + @some_setting.setter + def some_setting(self, value: int) -> None: + """ Setter for property "some_setting" + """ + pass + +.. + + **⚠ WARNING:** + + Note the order of decorators when declaring an abstract property. You + can not exchange ``@property`` and ``@abstractmethod``! + +This interface declares a method ``read_value`` and a read/write +property ``some_setting``. Note also that it inherits ``Base``. + +An actual hardware module implementation satisfying this interface could +look like below, assuming you placed the above code in +``qudi/interface/my_simple_interface.py``. ## Hardware Implementation: + +.. code:: python + + from qudi.interface.my_simple_interface import MySimpleInterface + + class MySimpleInstrument(MySimpleInterface): + """ Hardware module description and license/copyright header goes here + """ + + def on_activate(self): + """ Initialize module upon activation """ + # Perform any module initialization here, e.g. establish a connection to the instrument etc. + ... + + def on_deactivate(self): + """ Perform module cleanup upon deactivation """ + # Clean up your module and free all resources, e.g. terminate the instrument connection + ... + + def read_value(self) -> float: + """ Reads a value from the instrument and returns it """ + value = ... # Read a value from the instrument + return value + + @property + def some_setting(self) -> int: + """ Property holding some integer type setting in the instrument """ + value = ... # Retrieve the setting value from the instrument + return value + + @some_setting.setter + def some_setting(self, value: int) -> None: + """ Setter for property """ + ... # Perform some sanity checking apply the new setting value to the instrument + +Please note that in addition to the interface members you also needed to +implement ``on_activate`` and ``on_deactivate`` which are abstract +methods inherited from the ``Base`` class. + +| Implementing the members shown above is the bare minimum the hardware + class needs to provide. +| Of course, you can always implement additional members (helper methods + etc.) to structure your class in accordance with good programming + practices. + + **⚠ WARNING:** + + Other qudi modules that connect to a hardware class through an + interface must only ever use/call the members declared in the + interface. All additional members must only ever be used by the + hardware class internally. Consequentially, you should consider + making them protected or even private, i.e. add single ``_`` or + double underscore ``__`` name prefix, respectively. + +Why Do All This? +---------------- + +An abstract interface class defines a common set of methods and +properties to control and monitor a certain generalized type of hardware +(CW microwave sources, lasers, AWGs, etc.). + +Logic modules that orchestrate instruments via hardware modules usually +just define what kind of interface they want to use and not the specific +hardware module, i.e. they require a general type of hardware without +explicitly specifying a specific device model. + +| This has the advantage that you can exchange your instruments with + various devices of the same hardware type without changing the code + for your experiment procedure (i.e. the logic modules). As long as all + hardware modules use the same interface, you can freely exchange them + in the config while GUI and logic modules will work just the same. +| This is also called “hardware abstraction”. + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/logging.md b/docs/design_concepts/logging.md deleted file mode 100644 index c81140411..000000000 --- a/docs/design_concepts/logging.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Logging -Qudi uses the `logging` module from the Python standard library to manage log messages. Please -refer to the [official Python documentation](https://docs.python.org/3/library/logging.html) to get -familiar with the concept. - -Running qudi will initialize the `qudi.core.logger` module which configures the `logging` module -settings and installs log handlers in order to centrally manage all log messages. - -All log records are displayed in the qudi main window where they can also be filtered. -Log messages of level "error" or higher will additionally trigger a modal error dialog pop-up -displaying the error message and traceback. - -> **⚠ WARNING:** -> -> Qudi additionally installs a global `sys.excepthook` handler to catch all unhandled exceptions -> and prevent the application from terminating. -> -> All exceptions handled that way are diverted to the qudi logging facility with logging level -> "error". - -Furthermore, all log records are written into a text file located in the qudi subdirectory of the -user home directory (OS dependent). The log files of the last 5 qudi sessions are preserved. - -## Levels -The predefined most common logging levels are the same as described in the `logging` package -documentation: - -| Level Name | Numeric Value | -| ---------: | ------------- | -| critical | 50 | -| error | 40 | -| warn | 30 | -| info | 20 | -| debug | 10 | - -### critical -When a log record of level "critical" and higher is detected qudi will immediately attempt to kill -all running tasks and processes. - -In 99.99% of the cases there should be no reason to log a record of this level. So don't do it -unless you have very good reasons and know what you are doing. - -### error -Records of level "error" or higher should usually only be logged while handling an exception -(see: [exception handling guidelines](../404.md)). - -Unhandled exceptions will automatically be logged on the "error" level. - -### warn -Warning messages can be used as a tool by application programmers to point out possible problems, -e.g. an occurring edge case that is not yet well handled and often leads to errors. - -### info -Info messages should be used by an application programmer to inform the user of major events or -milestones in the intended program execution flow. - -> **⚠ WARNING:** -> -> There is a fine line between spam and useful information density. -> Think carefully what to log and use "debug" level while developing code and debugging. - -### debug -Use debug messages for information only interesting for programmers, e.g. to simplify debugging. -Records of this level and below are ignored by default and are not logged unless you run qudi in -debug mode (command line argument `--debug`). - -## Usage -The usual way to create a log record is to get a `logging.Logger` instance and call the level names -on it with the desired log message as argument, i.e.: -```python -.debug('my debug message') -.info('my info message') -.warn('my warning message') -.error('my error message') -.critical('my critical message') -``` - For this purpose you want to include the -traceback in the log record. You can do this easily by calling `exception` on the logger instance: -```python -try: - 0/0 -except ZeroDivisionError: - # This will include the traceback in the log record: - .exception('There was an exception while trying to divide two numbers') -``` - -### qudi Measurement Modules -The base class of any [qudi measurement module](measurement_modules.md) already provides you with -an appropriate `Logger` object that can be accessed with the `log` property: -```python -from qudi.core.module import LogicBase - -class MyExampleLogic(LogicBase): - """ Module description goes here """ - - ... - - def add_numbers(self, x, y): - """ Just a basic example method to add two numbers and log an info message. """ - result = x + y - self.log.info(f'Just added two number {x} and {y} resulting in {result}') - return result - - ... -``` - -### Other modules -For any module that is not a qudi measurement module but part of the `qudi` package namespace, you -should use `get_logger` from `qudi.core.logger` and initialize the logger with the modules -`__name__` attribute: -```python -from qudi.core.logger import get_logger - -logger = get_logger(__name__) -logger.info('Module initialized') # create example log record -``` -For any other Python module you can simply get a `Logger` object as described in the -[official Python documentation](https://docs.python.org/3/library/logging.html): -```python -from logging import getLogger - -logger = getLogger(__name__) -logger.info('Module initialized') # create example log record -``` - ---- - -[index](../index.md) diff --git a/docs/design_concepts/logging.rst b/docs/design_concepts/logging.rst new file mode 100644 index 000000000..eb3f6ee8e --- /dev/null +++ b/docs/design_concepts/logging.rst @@ -0,0 +1,177 @@ +`index <../index.md>`__ + +-------------- + +Logging +======= + +Qudi uses the ``logging`` module from the Python standard library to +manage log messages. Please refer to the `official Python +documentation `__ to get +familiar with the concept. + +Running qudi will initialize the ``qudi.core.logger`` module which +configures the ``logging`` module settings and installs log handlers in +order to centrally manage all log messages. + +| All log records are displayed in the qudi main window where they can + also be filtered. +| Log messages of level “error” or higher will additionally trigger a + modal error dialog pop-up displaying the error message and traceback. + + **⚠ WARNING:** + + Qudi additionally installs a global ``sys.excepthook`` handler to + catch all unhandled exceptions and prevent the application from + terminating. + + All exceptions handled that way are diverted to the qudi logging + facility with logging level “error”. + +Furthermore, all log records are written into a text file located in the +qudi subdirectory of the user home directory (OS dependent). The log +files of the last 5 qudi sessions are preserved. + +Levels +------ + +The predefined most common logging levels are the same as described in +the ``logging`` package documentation: + +========== ============= +Level Name Numeric Value +========== ============= +critical 50 +error 40 +warn 30 +info 20 +debug 10 +========== ============= + +critical +~~~~~~~~ + +When a log record of level “critical” and higher is detected qudi will +immediately attempt to kill all running tasks and processes. + +In 99.99% of the cases there should be no reason to log a record of this +level. So don’t do it unless you have very good reasons and know what +you are doing. + +error +~~~~~ + +Records of level “error” or higher should usually only be logged while +handling an exception (see: `exception handling +guidelines <../404.md>`__). + +Unhandled exceptions will automatically be logged on the “error” level. + +warn +~~~~ + +Warning messages can be used as a tool by application programmers to +point out possible problems, e.g. an occurring edge case that is not yet +well handled and often leads to errors. + +info +~~~~ + +Info messages should be used by an application programmer to inform the +user of major events or milestones in the intended program execution +flow. + + **⚠ WARNING:** + + | There is a fine line between spam and useful information density. + | Think carefully what to log and use “debug” level while developing + code and debugging. + +debug +~~~~~ + +Use debug messages for information only interesting for programmers, +e.g. to simplify debugging. Records of this level and below are ignored +by default and are not logged unless you run qudi in debug mode (command +line argument ``--debug``). + +Usage +----- + +The usual way to create a log record is to get a ``logging.Logger`` +instance and call the level names on it with the desired log message as +argument, i.e.: + +.. code:: python + + .debug('my debug message') + .info('my info message') + .warn('my warning message') + .error('my error message') + .critical('my critical message') + +For this purpose you want to include the traceback in the log record. +You can do this easily by calling ``exception`` on the logger instance: + +.. code:: python + + try: + 0/0 + except ZeroDivisionError: + # This will include the traceback in the log record: + .exception('There was an exception while trying to divide two numbers') + +qudi Measurement Modules +~~~~~~~~~~~~~~~~~~~~~~~~ + +The base class of any `qudi measurement +module `__ already provides you with an +appropriate ``Logger`` object that can be accessed with the ``log`` +property: + +.. code:: python + + from qudi.core.module import LogicBase + + class MyExampleLogic(LogicBase): + """ Module description goes here """ + + ... + + def add_numbers(self, x, y): + """ Just a basic example method to add two numbers and log an info message. """ + result = x + y + self.log.info(f'Just added two number {x} and {y} resulting in {result}') + return result + + ... + +Other modules +~~~~~~~~~~~~~ + +For any module that is not a qudi measurement module but part of the +``qudi`` package namespace, you should use ``get_logger`` from +``qudi.core.logger`` and initialize the logger with the modules +``__name__`` attribute: + +.. code:: python + + from qudi.core.logger import get_logger + + logger = get_logger(__name__) + logger.info('Module initialized') # create example log record + +For any other Python module you can simply get a ``Logger`` object as +described in the `official Python +documentation `__: + +.. code:: python + + from logging import getLogger + + logger = getLogger(__name__) + logger.info('Module initialized') # create example log record + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/measurement_modules.md b/docs/design_concepts/measurement_modules.md deleted file mode 100644 index 68b858b72..000000000 --- a/docs/design_concepts/measurement_modules.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Qudi Measurement Modules -Qudi user applications revolve around "qudi measurement modules", namely hardware, logic and -graphical user interface (GUI) modules. - -> **⚠ WARNING:** -> -> The term "module" is a bit misleading in this context since it usually refers to a `.py` file -> containing one or multiple definitions and/or statements. -> In our case a "qudi/measurement module" refers to a non-abstract subclass of `qudi.core.Base` -> (`LogicBase` and `GuiBase` are subclasses of `Base` as well) that is declared in a Python module -> usually located at `qudi/[hardware|logic|gui]/` -> (exception: [qudi extension](../404.md) directories). -> -> Historical reasons... you know... - -At the heart of each qudi measurement application stands a logic module. Logic modules -are the "brains" of each application and are responsible for control, configuration, monitoring, -data analysis and instrument orchestration to name only a few common tasks. - -If an application requires hardware instrumentation you also need qudi hardware modules to provide -abstracted hardware interfaces to logic modules. If you want to know more about hardware -abstraction in qudi, please read the [hardware interface documentation](hardware_interface.md). - -Having a logic module and possibly a hardware module is the bare minimum of a qudi measurement -application. The logic module provides a set of methods and properties that can be considered an -API for the specific measurement application, i.e. it provides full control over a certain type of -measurement. You can now control the logic via an interactive IPython session, e.g. a jupyter -notebook using the qudi kernel or the qudi manager GUI console. - -While a logic module is in principle enough to control a measurement application, you may want to -provide a more user-friendly interface. This is where qudi GUI modules come into play. -Each qudi GUI module must assemble and show a Qt `QMainWindow` instance that can use everything -[Qt for Python (PySide2)](https://doc.qt.io/qtforpython/) has to offer. -They must connect to at least one logic module in order to provide a graphical interface for it. - -> **⚠ WARNING:** -> -> GUI modules MUST NOT contain any "intelligent" code. -> In other words they MUST NOT facilitate any -> functionality that could not be achieved by using the bare logic module(s) it is connecting to. - - -## Module Features -All qudi measurement module classes are descendants of the abstract `qudi.core.module.Base` class -which provides the following features: - -### 1. Logging -Easy access to the qudi logging facility via their own logger object property `log`. -It can be used with all major log levels: -```python -.log.debug('debug message') # ignored by logger unless running in debug mode -.log.info('info message') -.log.warn('warning message') -.log.error('error message') # user prompt unless running in headless mode -.log.critical('critical message') # user prompt unless running in headless mode and - # qudi shutdown attempt -``` - -### 2. Finite State Machine -A very simple finite state machine (FSM) that can be accessed via property `module_state`: - -![FSM state diagram](../images/module_fsm_diagram.svg) - -The qudi [module manager](../404.md) uses this FSM to control and monitor qudi measurement modules. - -It can also be accessed by other entities in order to check if the measurement module is in `idle` -or `locked` state (i.e. if the module is busy). -The name of the current state can be polled by calling the FSM object: `.module_state()`. - -### 3. Thread Management -Logic modules (subclass of `qudi.core.module.LogicBase`) will run by default in their own thread. -Hardware and GUI modules will not live in their own threads by default. This is why it is so -important to facilitate [inter-module communication](../404.md) mainly with Qt Signals in order -to automatically respect thread affinity. - -You can access the Qt `QThread` object that represents the native thread of the module via the -property `module_thread`. - -To simply check if a module is running its own thread use the `bool` -property `is_module_threaded`. - -To manually alter thread affinity, you can explicitly declare `_threaded` in the class body of your -measurement module implementation to be `True` or `False` to let it run in its own thread or in -the main thread, respectively, i.e.: -```python -from qudi.core.module import Base - -class MyExampleModule(Base): - """ Description goes here """ - - _threaded = True # or alternatively False - ... -``` -Spawning and joining threads is handled automatically by the qudi [thread manager](../404.md). - -### 4. Balloon and Pop-Up Messaging -An easy way to notify the user with a message independent of the logging facility is provided via -the two utility methdos `_send_balloon_message` and `_send_pop_up_message`. - -By providing a title and message string to these methods, the user will either see a balloon -message (if supported by the OS) or a pop-up message with an OK button to dismiss, respectively. - -For balloon messages you can additionally provide a timeout and a `QIcon` instance to customize -the display duration and appearance. -Of course pop-up messages will not work if qudi is running in headless mode. In that case the -message will be printed out. This is also the behaviour if balloon messages are not supported -by the OS. - -### 5. Status Variables -Status variables (`qudi.core.module.StatusVar` members) are automatically dumped and loaded upon -deactivation and activation of the measurement module, respectively. - -In case you want to manually issue a dump of status variables, a module can call -`_dump_status_variables`. - -> **⚠ WARNING:** -> -> Please be aware that dumping status variables can potentially be slow depending on -the type and size of the variables. So think carefully before using manual dumping. - -See also the [qudi status variable documentation](../404.md). - -### 6. Static Configuration -Using qudi config options (`qudi.core.module.ConfigOption` members) one can facilitate static -configuration of your measurement modules. -Upon instantiation of a module, `ConfigOption` meta -variables are automatically initialized from the corresponding part of the current qudi config. -> **⚠ WARNING:** -> -> `ConfigOption` variables are only initialized once at the instantiation of the module and -NOT each time the module is activated. - -See also the [qudi configuration option documentation](../404.md). - -### 7. Measurement Module Interconnection -You can define other measurement modules that can be accessed via `Connector` meta object members. -The qudi module manager will automatically load and activate dependency modules according to the -configuration and connect them to the module upon activation. - -See also the [section further below](#inter-module-communication) for more info. - -### 8. Meta Information -Various read-only properties providing meta-information about the module: - -| property | description | -| ------------------------- | ----------------------------------------------------------------------------------------------- | -| `module_name` | The name given to the module by the currently loaded qudi configuration | -| `module_base` | The module base type identifier string (`'gui'`, `'logic'` or `'hardware'`) | -| `module_uuid` | A unique `UUID` that can be used to identify the module unambiguously | -| `module_default_data_dir` | The full path to the default module data directory. Can be overridden by module implementation. | - -### 9. Access to qudi main instance -Each measurement module holds a (weak) reference to the [`qudi.core.application.Qudi`](../404.md) -singleton instance. -This object holds references to all running core facilities like the currently loaded -`Configuration`, the `ModuleManager`, `ThreadManager` and the `rpyc` servers for remote module -and IPython kernel functionality. - -> **⚠ WARNING:** -> -> Designing a measurement module that needs to access the qudi application singleton is -generally considered bad practice. Unless you have a very specific and good reason to do so, -you should never use this object in your experiment toolchains. - - -## Inter-Module Communication -So, as you might have noticed the relationship of GUI, logic and hardware modules is hierarchical: -- GUI modules control one or more logic modules but no other GUI or hardware modules -- Logic modules control other logic modules and/or hardware modules but no GUI modules -- Hardware modules control no other qudi modules and are just providing an interface to a specific -instrument - -The connection to another module is done by the `qudi.core.connector.Connector` meta object. These -connectors declare the dependency of a module on another module further down the hierarchy, i.e. it -opens up a control flow path to another module. - -See the [qudi connectors documentation](connectors.md) for more details on how connectors work. - -Generally the control flow between modules should be signal-driven according to the -[Qt signal-slot principle](https://doc.qt.io/qt-5/signalsandslots.html). - -In the case of qudi this means a module should connect its own Qt signals to slots -(callback methods) in another module (unidirectional control flow) and connect signals from the -other module with its own slots (bidirectional control flow). -So, a modules can trigger the execution of a slot in another module. If both modules connected that -way are not running in the same thread all this will automatically happen asynchronously. -This is especially useful for GUI modules calling long-running logic methods/slots because they -would otherwise lock up and be unresponsive until the logic method has returned. - -A common example would be a GUI module triggering the start of a long-running logic method: -```python -from PySide2.QtCore import Signal -from qudi.core.module import Base, LogicBase -from qudi.core.connector import Connector - -# GUI module declaration in e.g. qudi/gui/my_gui_module.py -class MyGuiModule(Base): - """ Description goes here """ - - # Qt signal triggering the start of the measurement - sigStartMeasurement = Signal() - - # Connector to get a reference to the measurement logic module - _logic_connector = Connector(interface='MyLogicModule', name='my_logic') - - ... - - def on_activate(self): - self.sigStartMeasurement.connect(self._logic_connector().start_measurement) - self._logic_connector().sigMeasurementFinished.connect(self._measurement_finished) - - def trigger_measurement_start(self): - """ Will just emit the sigStartMeasurement signal """ - self.sigStartMeasurement.emit() - - def _measurement_finished(self): - """ Callback for measurement finished signal from logic module """ - print('Logic has finished the measurement') - - ... - -# Logic module declaration in e.g. qudi/logic/my_logic_module.py -class MyLogicModule(LogicBase): - """ Description goes here """ - - # Qt signal notifying all connected "listeners" about a finished measurement - sigMeasurementFinished = Signal() - - ... - - def start_measurement(self): - """ API method to start a measurement """ - # Actually perform your measurement here and emit notification signal upon finishing - self.sigMeasurementFinished.emit() - - ... -``` -In the above example, a GUI call to `trigger_measurement_start` will return immediately and cause -the logic module to asynchronously start the measurement by running `start_measurement`. -While the measurement is running in the logic thread, the GUI module stays responsive and can -perform other tasks. -As soon as the logic module has finished its measurement it will emit a signal causing all -connected slots to be called asynchronously in their respective threads. In our case this will -execute the `_measurement_finished` callback and print the message. - -The same kind of control flow can be established between multiple logic modules, each running in -its own thread. - -There is an exception to this kind of control flow... hardware modules. -Hardware modules usually just provide a set of wrapper methods to control an instrument and are -typically controlled by logic modules that run in their own thread. So in most cases there is no -need to access hardware functionality asynchronously and the logic can thus simply access the -hardware directly via its connector (without signal/slot mechanics): -```python -from qudi.core.module import LogicBase -from qudi.core.connector import Connector - - -class MyLogicModule(LogicBase): - """ Description goes here """ - - # Connector to get a reference to the hardware module - _hardware_connector = Connector(interface='MyHardwareInterface', name='my_hardware') - - ... - - def do_stuff_in_hardware(self): - """ Will perform some task using the connected hardware """ - self._hardware_connector().do_stuff() # direct method call, no signal/slot shenanigans - - ... -``` - - ---- - -[index](../index.md) diff --git a/docs/design_concepts/measurement_modules.rst b/docs/design_concepts/measurement_modules.rst new file mode 100644 index 000000000..091e63126 --- /dev/null +++ b/docs/design_concepts/measurement_modules.rst @@ -0,0 +1,361 @@ +`index <../index.md>`__ + +-------------- + +Qudi Measurement Modules +======================== + +Qudi user applications revolve around “qudi measurement modules”, namely +hardware, logic and graphical user interface (GUI) modules. + + **⚠ WARNING:** + + | The term “module” is a bit misleading in this context since it + usually refers to a ``.py`` file containing one or multiple + definitions and/or statements. + | In our case a “qudi/measurement module” refers to a non-abstract + subclass of ``qudi.core.Base`` (``LogicBase`` and ``GuiBase`` are + subclasses of ``Base`` as well) that is declared in a Python module + usually located at ``qudi/[hardware|logic|gui]/`` (exception: `qudi + extension <../404.md>`__ directories). + + Historical reasons… you know… + +At the heart of each qudi measurement application stands a logic module. +Logic modules are the “brains” of each application and are responsible +for control, configuration, monitoring, data analysis and instrument +orchestration to name only a few common tasks. + +If an application requires hardware instrumentation you also need qudi +hardware modules to provide abstracted hardware interfaces to logic +modules. If you want to know more about hardware abstraction in qudi, +please read the `hardware interface +documentation `__. + +Having a logic module and possibly a hardware module is the bare minimum +of a qudi measurement application. The logic module provides a set of +methods and properties that can be considered an API for the specific +measurement application, i.e. it provides full control over a certain +type of measurement. You can now control the logic via an interactive +IPython session, e.g. a jupyter notebook using the qudi kernel or the +qudi manager GUI console. + +| While a logic module is in principle enough to control a measurement + application, you may want to provide a more user-friendly interface. + This is where qudi GUI modules come into play. Each qudi GUI module + must assemble and show a Qt ``QMainWindow`` instance that can use + everything `Qt for Python + (PySide2) `__ has to offer. +| They must connect to at least one logic module in order to provide a + graphical interface for it. + + **⚠ WARNING:** + + | GUI modules MUST NOT contain any “intelligent” code. + | In other words they MUST NOT facilitate any functionality that + could not be achieved by using the bare logic module(s) it is + connecting to. + +Module Features +--------------- + +All qudi measurement module classes are descendants of the abstract +``qudi.core.module.Base`` class which provides the following features: + +1. Logging +~~~~~~~~~~ + +| Easy access to the qudi logging facility via their own logger object + property ``log``. +| It can be used with all major log levels: + +.. code:: python + + .log.debug('debug message') # ignored by logger unless running in debug mode + .log.info('info message') + .log.warn('warning message') + .log.error('error message') # user prompt unless running in headless mode + .log.critical('critical message') # user prompt unless running in headless mode and + # qudi shutdown attempt + +2. Finite State Machine +~~~~~~~~~~~~~~~~~~~~~~~ + +A very simple finite state machine (FSM) that can be accessed via +property ``module_state``: + +.. figure:: ../images/module_fsm_diagram.svg + :alt: FSM state diagram + + FSM state diagram + +The qudi `module manager <../404.md>`__ uses this FSM to control and +monitor qudi measurement modules. + +| It can also be accessed by other entities in order to check if the + measurement module is in ``idle`` or ``locked`` state (i.e. if the + module is busy). +| The name of the current state can be polled by calling the FSM object: + ``.module_state()``. + +3. Thread Management +~~~~~~~~~~~~~~~~~~~~ + +Logic modules (subclass of ``qudi.core.module.LogicBase``) will run by +default in their own thread. Hardware and GUI modules will not live in +their own threads by default. This is why it is so important to +facilitate `inter-module communication <../404.md>`__ mainly with Qt +Signals in order to automatically respect thread affinity. + +You can access the Qt ``QThread`` object that represents the native +thread of the module via the property ``module_thread``. + +To simply check if a module is running its own thread use the ``bool`` +property ``is_module_threaded``. + +To manually alter thread affinity, you can explicitly declare +``_threaded`` in the class body of your measurement module +implementation to be ``True`` or ``False`` to let it run in its own +thread or in the main thread, respectively, i.e.: + +.. code:: python + + from qudi.core.module import Base + + class MyExampleModule(Base): + """ Description goes here """ + + _threaded = True # or alternatively False + ... + +Spawning and joining threads is handled automatically by the qudi +`thread manager <../404.md>`__. + +4. Balloon and Pop-Up Messaging +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An easy way to notify the user with a message independent of the logging +facility is provided via the two utility methdos +``_send_balloon_message`` and ``_send_pop_up_message``. + +By providing a title and message string to these methods, the user will +either see a balloon message (if supported by the OS) or a pop-up +message with an OK button to dismiss, respectively. + +| For balloon messages you can additionally provide a timeout and a + ``QIcon`` instance to customize the display duration and appearance. +| Of course pop-up messages will not work if qudi is running in headless + mode. In that case the message will be printed out. This is also the + behaviour if balloon messages are not supported by the OS. + +5. Status Variables +~~~~~~~~~~~~~~~~~~~ + +Status variables (``qudi.core.module.StatusVar`` members) are +automatically dumped and loaded upon deactivation and activation of the +measurement module, respectively. + +In case you want to manually issue a dump of status variables, a module +can call ``_dump_status_variables``. + + **⚠ WARNING:** + + Please be aware that dumping status variables can potentially be slow + depending on the type and size of the variables. So think carefully + before using manual dumping. + +See also the `qudi status variable documentation <../404.md>`__. + +6. Static Configuration +~~~~~~~~~~~~~~~~~~~~~~~ + +| Using qudi config options (``qudi.core.module.ConfigOption`` members) + one can facilitate static configuration of your measurement modules. +| Upon instantiation of a module, ``ConfigOption`` meta variables are + automatically initialized from the corresponding part of the current + qudi config. > **⚠ WARNING:** > > ``ConfigOption`` variables are only + initialized once at the instantiation of the module and NOT each time + the module is activated. + +See also the `qudi configuration option documentation <../404.md>`__. + +7. Measurement Module Interconnection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +| You can define other measurement modules that can be accessed via + ``Connector`` meta object members. +| The qudi module manager will automatically load and activate + dependency modules according to the configuration and connect them to + the module upon activation. + +See also the `section further below <#inter-module-communication>`__ for +more info. + +8. Meta Information +~~~~~~~~~~~~~~~~~~~ + +Various read-only properties providing meta-information about the +module: + ++--------------+--------------------------------------------------------+ +| property | description | ++==============+========================================================+ +| ``m | The name given to the module by the currently loaded | +| odule_name`` | qudi configuration | ++--------------+--------------------------------------------------------+ +| ``m | The module base type identifier string (``'gui'``, | +| odule_base`` | ``'logic'`` or ``'hardware'``) | ++--------------+--------------------------------------------------------+ +| ``m | A unique ``UUID`` that can be used to identify the | +| odule_uuid`` | module unambiguously | ++--------------+--------------------------------------------------------+ +| ``m | The full path to the default module data directory. | +| odule_defaul | Can be overridden by module implementation. | +| t_data_dir`` | | ++--------------+--------------------------------------------------------+ + +9. Access to qudi main instance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each measurement module holds a (weak) reference to the +```qudi.core.application.Qudi`` <../404.md>`__ singleton instance. This +object holds references to all running core facilities like the +currently loaded ``Configuration``, the ``ModuleManager``, +``ThreadManager`` and the ``rpyc`` servers for remote module and IPython +kernel functionality. + + **⚠ WARNING:** + + Designing a measurement module that needs to access the qudi + application singleton is generally considered bad practice. Unless + you have a very specific and good reason to do so, you should never + use this object in your experiment toolchains. + +Inter-Module Communication +-------------------------- + +So, as you might have noticed the relationship of GUI, logic and +hardware modules is hierarchical: - GUI modules control one or more +logic modules but no other GUI or hardware modules - Logic modules +control other logic modules and/or hardware modules but no GUI modules - +Hardware modules control no other qudi modules and are just providing an +interface to a specific instrument + +The connection to another module is done by the +``qudi.core.connector.Connector`` meta object. These connectors declare +the dependency of a module on another module further down the hierarchy, +i.e. it opens up a control flow path to another module. + +See the `qudi connectors documentation `__ for more +details on how connectors work. + +Generally the control flow between modules should be signal-driven +according to the `Qt signal-slot +principle `__. + +| In the case of qudi this means a module should connect its own Qt + signals to slots (callback methods) in another module (unidirectional + control flow) and connect signals from the other module with its own + slots (bidirectional control flow). +| So, a modules can trigger the execution of a slot in another module. + If both modules connected that way are not running in the same thread + all this will automatically happen asynchronously. +| This is especially useful for GUI modules calling long-running logic + methods/slots because they would otherwise lock up and be unresponsive + until the logic method has returned. + +A common example would be a GUI module triggering the start of a +long-running logic method: + +.. code:: python + + from PySide2.QtCore import Signal + from qudi.core.module import Base, LogicBase + from qudi.core.connector import Connector + + # GUI module declaration in e.g. qudi/gui/my_gui_module.py + class MyGuiModule(Base): + """ Description goes here """ + + # Qt signal triggering the start of the measurement + sigStartMeasurement = Signal() + + # Connector to get a reference to the measurement logic module + _logic_connector = Connector(interface='MyLogicModule', name='my_logic') + + ... + + def on_activate(self): + self.sigStartMeasurement.connect(self._logic_connector().start_measurement) + self._logic_connector().sigMeasurementFinished.connect(self._measurement_finished) + + def trigger_measurement_start(self): + """ Will just emit the sigStartMeasurement signal """ + self.sigStartMeasurement.emit() + + def _measurement_finished(self): + """ Callback for measurement finished signal from logic module """ + print('Logic has finished the measurement') + + ... + + # Logic module declaration in e.g. qudi/logic/my_logic_module.py + class MyLogicModule(LogicBase): + """ Description goes here """ + + # Qt signal notifying all connected "listeners" about a finished measurement + sigMeasurementFinished = Signal() + + ... + + def start_measurement(self): + """ API method to start a measurement """ + # Actually perform your measurement here and emit notification signal upon finishing + self.sigMeasurementFinished.emit() + + ... + +| In the above example, a GUI call to ``trigger_measurement_start`` will + return immediately and cause the logic module to asynchronously start + the measurement by running ``start_measurement``. +| While the measurement is running in the logic thread, the GUI module + stays responsive and can perform other tasks. +| As soon as the logic module has finished its measurement it will emit + a signal causing all connected slots to be called asynchronously in + their respective threads. In our case this will execute the + ``_measurement_finished`` callback and print the message. + +The same kind of control flow can be established between multiple logic +modules, each running in its own thread. + +| There is an exception to this kind of control flow… hardware modules. +| Hardware modules usually just provide a set of wrapper methods to + control an instrument and are typically controlled by logic modules + that run in their own thread. So in most cases there is no need to + access hardware functionality asynchronously and the logic can thus + simply access the hardware directly via its connector (without + signal/slot mechanics): + +.. code:: python + + from qudi.core.module import LogicBase + from qudi.core.connector import Connector + + + class MyLogicModule(LogicBase): + """ Description goes here """ + + # Connector to get a reference to the hardware module + _hardware_connector = Connector(interface='MyHardwareInterface', name='my_hardware') + + ... + + def do_stuff_in_hardware(self): + """ Will perform some task using the connected hardware """ + self._hardware_connector().do_stuff() # direct method call, no signal/slot shenanigans + + ... + +-------------- + +`index <../index.md>`__ diff --git a/docs/design_concepts/remote_modules.md b/docs/design_concepts/remote_modules.md deleted file mode 100644 index fa6b27f03..000000000 --- a/docs/design_concepts/remote_modules.md +++ /dev/null @@ -1,47 +0,0 @@ -# Remote Modules - -Qudi supports accessing modules of a qudi instance that is running on a different (remote) computer within the same LAN. A possible configuration for this looks like: - -## Server - - - global: - remote_modules_server: - address: "ip.address.of.this.machine" - port: port_of_the_server(int) - - hardware: - name_of_hardware: - module.class: "hardwarefile.classname" - allow_remote: True - options: - ... - -## Client - - global: - force_remote_calls_by_value: True - # If this flag is set (True), all arguments passed to qudi module APIs from remote - # (jupyter notebook, qudi console, remote modules) will be wrapped and passed "per value" - # (serialized and de-serialized). This is avoiding a lot of inconveniences with using numpy in - # remote clients. - # If you do not want to use this workaround and know what you are doing, you can disable this - # feature by setting this flag to False. - - hardware: - remote_hardware: - native_module_name: "name of module in server's config file" - address: "ip.address.of.remote.hardware.server" - port: port_of_remote_hardware_server(int) - -For a more elaborate explanation refer to the [configuration documentation](https://github.com/Ulm-IQO/qudi-core/blob/main/docs/design_concepts/configuration.md). -Moreover, subsequent processing of python objects in a corresponding logic on the client side may need a preliminary netobtain, which migth look like this: - - from qudi.util.network import netobtain - ... - data = ...get_data_from_remote_hardware() - data = netobatin(data) - -For a detailed explanation refer to the rpyc (netref) [documentation](https://rpyc.readthedocs.io/en/latest/index.html). - -In case you can not access your remote module, it might be also worth checking your firewall settings and the ethernet adapter settings (public/private network) of your machines. diff --git a/docs/design_concepts/remote_modules.rst b/docs/design_concepts/remote_modules.rst new file mode 100644 index 000000000..dea41c57c --- /dev/null +++ b/docs/design_concepts/remote_modules.rst @@ -0,0 +1,63 @@ +Remote Modules +============== + +Qudi supports accessing modules of a qudi instance that is running on a +different (remote) computer within the same LAN. A possible +configuration for this looks like: + +Server +------ + +:: + + global: + remote_modules_server: + address: "ip.address.of.this.machine" + port: port_of_the_server(int) + + hardware: + name_of_hardware: + module.class: "hardwarefile.classname" + allow_remote: True + options: + ... + +Client +------ + +:: + + global: + force_remote_calls_by_value: True + # If this flag is set (True), all arguments passed to qudi module APIs from remote + # (jupyter notebook, qudi console, remote modules) will be wrapped and passed "per value" + # (serialized and de-serialized). This is avoiding a lot of inconveniences with using numpy in + # remote clients. + # If you do not want to use this workaround and know what you are doing, you can disable this + # feature by setting this flag to False. + + hardware: + remote_hardware: + native_module_name: "name of module in server's config file" + address: "ip.address.of.remote.hardware.server" + port: port_of_remote_hardware_server(int) + +For a more elaborate explanation refer to the `configuration +documentation `__. +Moreover, subsequent processing of python objects in a corresponding +logic on the client side may need a preliminary netobtain, which migth +look like this: + +:: + + from qudi.util.network import netobtain + ... + data = ...get_data_from_remote_hardware() + data = netobatin(data) + +For a detailed explanation refer to the rpyc (netref) +`documentation `__. + +In case you can not access your remote module, it might be also worth +checking your firewall settings and the ethernet adapter settings +(public/private network) of your machines. diff --git a/docs/design_concepts/status_variables.md b/docs/design_concepts/status_variables.md deleted file mode 100644 index 6e79b66c7..000000000 --- a/docs/design_concepts/status_variables.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -layout: default -title: qudi-core ---- - -[index](../index.md) - ---- - -# Status Variables - -When working with [measurement modules](measurement_modules.md) (hardware/logic/GUI) you may want -to preserve some variable values across consecutive runs of qudi. - -We call measurement module instance variables that are automatically dumped/loaded upon -deactivation/activation a "status variable". - -The parent `Base` class will take care of dumping all status variables upon deactivation of the -module. This will happen automatically at the end of `on_deactivate` and does NOT need to be -triggered explicitly in the `on_deactivate` method definition. -This will also happen in case `on_deactivate` raises an exception. - -> **⚠ WARNING:** -> -> Status variables will NOT be automatically saved if the measurement modules are not deactivated -> properly (i.e. by the module manager). -> This can happen for example if the user is just killing the qudi process instead of shutting it -> down as intended (e.g. by pressing the "stop" button in the PyCharm IDE). -> -> If this happens, the next startup of the modules will load the status variables from the last -> graceful deactivation. - -Upon module activation, immediately before `on_activate` is run, status variables are read from -disk and initialized in the module instance. This means that `on_activate` can already use these -variables. -If there are any exceptions raised during this process, the module activation will still proceed -but the status variable will be initialized with its default value instead if defined. This can -however easily lead to follow-up errors in `on_activate`. - -The status variables are stored in YAML format in one file per module using the qudi utilities in -`qudi.util.yaml`. They are stored in an OS dependent qudi "AppData" directory. - -## Usage -In order to simplify the process of dumping/loading these variables to/from disk and prevent each -measurement module to implement their own solution, qudi provides the meta object -`qudi.core.statusvariable.StatusVar`. - -When implementing a [measurement module](measurement_modules.md) (hardware/logic/GUI) you can -simply instantiate `StatusVar` class variables. These meta objects will be transformed into -regular variable members of your measurement module instances and can be used like any normal -instance variable in Python: -```python -from qudi.core.statusvariable import StatusVar -from qudi.core.module import LogicBase - -class MyExampleLogic(LogicBase): - """ Module description goes here """ - - _my_status_variable = StatusVar(name='my_storage_name', default=42,) - - ... - - def increment_my_variable(self): - self._my_status_variable += 1 - - ... -``` - -### constructor & representer -Since status variables are dumped as YAML file you are limited in what data types can be stored. -Qudi YAML dumper and loader currently supports any native Python builtin type and numpy arrays. - -If your status variables should be of any other type, you need to provide conversion functions to -the `StatusVar` meta object: -- The `constructor` is a callable that accepts a simplified variable from the YAML loader and -returns the data as custom data type to initialize the status variable with. -- The `representer` is a callable that accepts the custom status variable data and returns a -simplified data representation that is digestible by the YAML dumper. - -You can provide `constructor` and `representer` callables as arguments to `StatusVar` or you can -register a callable member of your module as such via decorators, e.g.: -```python -from qudi.core.statusvariable import StatusVar -from qudi.core.module import LogicBase - -class FancyDataType: - def __init__(self, a, b): - self.a = a - self.b = b - - -class MyExampleLogic(LogicBase): - """ Module description goes here """ - - _my_status_variable = StatusVar(default=FancyDataType(42, 3.1415)) - _my_other_status_variable = StatusVar( - default=FancyDataType(1, 2), - constructor=lambda yaml_data: FancyDataType(*yaml_data), - representer=lambda data: [data.a, data.b] - ) - - ... - - @_my_status_variable.constructor - def my_status_variable_constructor(self, yaml_data): - return FancyDataType(*yaml_data) - - @_my_status_variable.representer - def my_status_variable_constructor(self, data): - return [data.a, data.b] - - ... -``` -Since these conversion functions are usually static (as the example above also shows), you could -also combine that with the `@staticmethod` decorator. But this is not necessary and just good style. - -### name -There is an optional `name` argument for `StatusVar`. The name given here is used by the YAML -dumper as field name for the variable data. So the `name` argument can be used to store the status -variable under a different (e.g. better readable) name in the app status file that is created. -A common use case is (as shown in the example above) to exclude the Pythonic (double-)underscore -from the variable name. -By default, the declared variable name in the class body will be used and since the user usually -never opens the AppStatus files, this feature is not quite too useful. - ---- - -[index](../index.md) diff --git a/docs/design_concepts/status_variables.rst b/docs/design_concepts/status_variables.rst new file mode 100644 index 000000000..f322dd90f --- /dev/null +++ b/docs/design_concepts/status_variables.rst @@ -0,0 +1,150 @@ +`index <../index.md>`__ + +-------------- + +Status Variables +================ + +When working with `measurement modules `__ +(hardware/logic/GUI) you may want to preserve some variable values +across consecutive runs of qudi. + +We call measurement module instance variables that are automatically +dumped/loaded upon deactivation/activation a “status variable”. + +| The parent ``Base`` class will take care of dumping all status + variables upon deactivation of the module. This will happen + automatically at the end of ``on_deactivate`` and does NOT need to be + triggered explicitly in the ``on_deactivate`` method definition. +| This will also happen in case ``on_deactivate`` raises an exception. + + **⚠ WARNING:** + + | Status variables will NOT be automatically saved if the measurement + modules are not deactivated properly (i.e. by the module manager). + | This can happen for example if the user is just killing the qudi + process instead of shutting it down as intended (e.g. by pressing + the “stop” button in the PyCharm IDE). + + If this happens, the next startup of the modules will load the status + variables from the last graceful deactivation. + +| Upon module activation, immediately before ``on_activate`` is run, + status variables are read from disk and initialized in the module + instance. This means that ``on_activate`` can already use these + variables. +| If there are any exceptions raised during this process, the module + activation will still proceed but the status variable will be + initialized with its default value instead if defined. This can + however easily lead to follow-up errors in ``on_activate``. + +The status variables are stored in YAML format in one file per module +using the qudi utilities in ``qudi.util.yaml``. They are stored in an OS +dependent qudi “AppData” directory. + +Usage +----- + +In order to simplify the process of dumping/loading these variables +to/from disk and prevent each measurement module to implement their own +solution, qudi provides the meta object +``qudi.core.statusvariable.StatusVar``. + +When implementing a `measurement module `__ +(hardware/logic/GUI) you can simply instantiate ``StatusVar`` class +variables. These meta objects will be transformed into regular variable +members of your measurement module instances and can be used like any +normal instance variable in Python: + +.. code:: python + + from qudi.core.statusvariable import StatusVar + from qudi.core.module import LogicBase + + class MyExampleLogic(LogicBase): + """ Module description goes here """ + + _my_status_variable = StatusVar(name='my_storage_name', default=42,) + + ... + + def increment_my_variable(self): + self._my_status_variable += 1 + + ... + +constructor & representer +~~~~~~~~~~~~~~~~~~~~~~~~~ + +| Since status variables are dumped as YAML file you are limited in what + data types can be stored. +| Qudi YAML dumper and loader currently supports any native Python + builtin type and numpy arrays. + +If your status variables should be of any other type, you need to +provide conversion functions to the ``StatusVar`` meta object: - The +``constructor`` is a callable that accepts a simplified variable from +the YAML loader and returns the data as custom data type to initialize +the status variable with. - The ``representer`` is a callable that +accepts the custom status variable data and returns a simplified data +representation that is digestible by the YAML dumper. + +You can provide ``constructor`` and ``representer`` callables as +arguments to ``StatusVar`` or you can register a callable member of your +module as such via decorators, e.g.: + +.. code:: python + + from qudi.core.statusvariable import StatusVar + from qudi.core.module import LogicBase + + class FancyDataType: + def __init__(self, a, b): + self.a = a + self.b = b + + + class MyExampleLogic(LogicBase): + """ Module description goes here """ + + _my_status_variable = StatusVar(default=FancyDataType(42, 3.1415)) + _my_other_status_variable = StatusVar( + default=FancyDataType(1, 2), + constructor=lambda yaml_data: FancyDataType(*yaml_data), + representer=lambda data: [data.a, data.b] + ) + + ... + + @_my_status_variable.constructor + def my_status_variable_constructor(self, yaml_data): + return FancyDataType(*yaml_data) + + @_my_status_variable.representer + def my_status_variable_constructor(self, data): + return [data.a, data.b] + + ... + +Since these conversion functions are usually static (as the example +above also shows), you could also combine that with the +``@staticmethod`` decorator. But this is not necessary and just good +style. + +name +~~~~ + +| There is an optional ``name`` argument for ``StatusVar``. The name + given here is used by the YAML dumper as field name for the variable + data. So the ``name`` argument can be used to store the status + variable under a different (e.g. better readable) name in the app + status file that is created. +| A common use case is (as shown in the example above) to exclude the + Pythonic (double-)underscore from the variable name. +| By default, the declared variable name in the class body will be used + and since the user usually never opens the AppStatus files, this + feature is not quite too useful. + +-------------- + +`index <../index.md>`__ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index cb289105e..000000000 --- a/docs/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# Welcome to the Qudi Core Documentation ----- - -[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) - -```{toctree} ---- -maxdepth: 2 -glob: ---- - -getting_started.md -license.md - -setup/installation.md -setup/startup.md -setup/jupyter.md - -design_concepts/* - - - -``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..fb162942b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +Welcome to Qudi Core's documentation! +===================================== + +.. toctree:: + :maxdepth: 1 + :glob: + :caption: Design Concepts: + + design_concepts/* + +.. autosummary:: + :toctree: _autosummary + :template: custom-module-template.rst + :recursive: + + qudi + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` \ No newline at end of file diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt deleted file mode 100644 index 026451530..000000000 --- a/docs/requirements-docs.txt +++ /dev/null @@ -1,20 +0,0 @@ -wheel>=0.37.0 -cycler>=0.10.0 -entrypoints>=0.3 -fysom>=2.1.6 -GitPython>=3.1.24 -jupyter>=1.0.0 -jupytext>=1.13.0 -lmfit>=1.0.3 -matplotlib>=3.4.3 -numpy>=1.21.3 -pyqtgraph>=0.13.0 -PySide2==5.15.2.1 -rpyc>=5.0.1 -ruamel.yaml>=0.17.16 -scipy>=1.7.1 -jsonschema>=4.2.1 -Sphinx==7.2.6 -numpydoc==1.6.0 -pydata_sphinx_theme==0.15.2 -myst-parser==2.0.0 diff --git a/docs/templates/custom-class-template.rst b/docs/templates/custom-class-template.rst new file mode 100644 index 000000000..23e304688 --- /dev/null +++ b/docs/templates/custom-class-template.rst @@ -0,0 +1,36 @@ +.. + BLESS THIS GUY + https://stackoverflow.com/questions/2701998/automatically-document-all-modules-recursively-with-sphinx-autodoc/62613202#62613202 + +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :show-inheritance: + :inherited-members: + + {% block methods %} + .. automethod:: __init__ + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} \ No newline at end of file diff --git a/docs/templates/custom-module-template.rst b/docs/templates/custom-module-template.rst new file mode 100644 index 000000000..0e8c2d217 --- /dev/null +++ b/docs/templates/custom-module-template.rst @@ -0,0 +1,70 @@ +.. + BLESS THIS GUY + https://stackoverflow.com/questions/2701998/automatically-document-all-modules-recursively-with-sphinx-autodoc/62613202#62613202 + +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Module Attributes + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + :template: custom-class-template.rst + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4001ce706..3f9bb7074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,9 +76,17 @@ dependencies = [ dev-docs = [ "Sphinx==7.2.6", "numpydoc==1.6.0", - "pydata_sphinx_theme==0.15.2", + "sphinx-rtd-dark-mode==1.3.0", + "sphinx-rtd-theme==2.0.0", "myst-parser==2.0.0", ] -# sphinx-book-theme = ["sphinx-book-theme==1.1.1"] -# sphinx-rtd-theme = ["sphinx-rtd-theme==2.0.0"] + +[project.scripts] +qudi = 'qudi.runnable:main' +qudi-config-editor = 'qudi.tools.config_editor.config_editor:main' +qudi-uninstall-kernel = 'qudi.core.qudikernel:uninstall_kernel' +qudi-install-kernel = 'qudi.core.qudikernel:install_kernel' + +# "sphinx-book-theme==1.1.1" # nbsphinx = "^0.8.8" +# "pydata_sphinx_theme==0.15.2" diff --git a/src/qudi/core/__main__.py b/src/qudi/core/__main__.py index 28f9e6678..1153c0a87 100644 --- a/src/qudi/core/__main__.py +++ b/src/qudi/core/__main__.py @@ -17,36 +17,38 @@ If not, see . """ -import argparse -from qudi.core.application import Qudi +if __name__ == '__main__': + + import argparse + from qudi.core.application import Qudi -# parse commandline parameters -parser = argparse.ArgumentParser(prog='python -m qudi.core') -parser.add_argument( - '-g', - '--no-gui', - action='store_true', - help='Run qudi "headless", i.e. without GUI. User interaction only possible via IPython kernel.' -) -parser.add_argument( - '-d', - '--debug', - action='store_true', - help='Run qudi in debug mode to log all debug messages. Can affect performance.' -) -parser.add_argument( - '-c', - '--config', - default=None, - help='Path to the configuration file to use for for this qudi session.' -) -parser.add_argument( - '-l', - '--logdir', - default='', - help='Absolute path to log directory to use instead of the default one "/qudi/log/"' -) -args = parser.parse_args() + # parse commandline parameters + parser = argparse.ArgumentParser(prog='python -m qudi.core') + parser.add_argument( + '-g', + '--no-gui', + action='store_true', + help='Run qudi "headless", i.e. without GUI. User interaction only possible via IPython kernel.' + ) + parser.add_argument( + '-d', + '--debug', + action='store_true', + help='Run qudi in debug mode to log all debug messages. Can affect performance.' + ) + parser.add_argument( + '-c', + '--config', + default=None, + help='Path to the configuration file to use for for this qudi session.' + ) + parser.add_argument( + '-l', + '--logdir', + default='', + help='Absolute path to log directory to use instead of the default one "/qudi/log/"' + ) + args = parser.parse_args() -app = Qudi(no_gui=args.no_gui, debug=args.debug, log_dir=args.logdir, config_file=args.config) -app.run() + app = Qudi(no_gui=args.no_gui, debug=args.debug, log_dir=args.logdir, config_file=args.config) + app.run() diff --git a/src/qudi/gui/taskrunner/__init__.py b/src/qudi/gui/taskrunner/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qudi/logic/__init__.py b/src/qudi/logic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qudi/tasks/__init__.py b/src/qudi/tasks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qudi/tools/config_editor/__main__.py b/src/qudi/tools/config_editor/__main__.py index dda977d9d..94c24eca9 100644 --- a/src/qudi/tools/config_editor/__main__.py +++ b/src/qudi/tools/config_editor/__main__.py @@ -17,6 +17,7 @@ If not, see . """ -from .config_editor import main +if __name__ == "__main__": + from .config_editor import main -main() + main() diff --git a/src/qudi/util/__init__.py b/src/qudi/util/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qudi/util/fit_models/__init__.py b/src/qudi/util/fit_models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qudi/util/parameters.py b/src/qudi/util/parameters.py index 8a98264c9..2b129ce45 100644 --- a/src/qudi/util/parameters.py +++ b/src/qudi/util/parameters.py @@ -3,19 +3,21 @@ This file contains utility methods to annotate arguments for which the user can potentially edit values via GUI. These arguments are boiled down to simple builtin types that can be represented by a GUI editor: - int: qudi.util.widgets.scientific_spinbox.ScienSpinBox - float: qudi.util.widgets.scientific_spinbox.ScienDSpinbox - str: PySide2.QtWidgets.QLineEdit - complex: qudi.util.widgets.literal_lineedit.ComplexLineEdit - set: qudi.util.widgets.literal_lineedit.SetLineEdit - dict: qudi.util.widgets.literal_lineedit.DictLineEdit - list: qudi.util.widgets.literal_lineedit.ListLineEdit - tuple: qudi.util.widgets.literal_lineedit.TupleLineEdit + +:int: :py:class:`qudi.util.widgets.scientific_spinbox.ScienSpinBox` +:float: :py:class:`qudi.util.widgets.scientific_spinbox.ScienDSpinbox` +:str: :py:class:`PySide2.QtWidgets.QLineEdit` +:complex: :py:class:`qudi.util.widgets.literal_lineedit.ComplexLineEdit` +:set: :py:class:`qudi.util.widgets.literal_lineedit.SetLineEdit` +:dict: :py:class:`qudi.util.widgets.literal_lineedit.DictLineEdit` +:list: :py:class:`qudi.util.widgets.literal_lineedit.ListLineEdit` +:tuple: :py:class:`qudi.util.widgets.literal_lineedit.TupleLineEdit` Here defined are also custom generic types that can be used like any other type from the typing module. Arguments annotated with these types are represented with the following widgets: - FilePath: PySide2.QtWidgets.QLineEdit + +FilePath: PySide2.QtWidgets.QLineEdit Any other type annotation that can not be mapped to a simple builtin or a custom generic type (see above) will result in no corresponding widget (None). From ebaac4ba82c3270fe53c848a635e986ee41d0ec6 Mon Sep 17 00:00:00 2001 From: Prithvi Gundlapalli Date: Thu, 15 Feb 2024 17:45:29 +0100 Subject: [PATCH 09/10] Adding a badge for rtd build status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e6a218dcc..94d9d4e70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # qudi-core [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) ![PyPI release](https://github.com/Ulm-IQO/qudi-core/actions/workflows/release_pypi.yml/badge.svg) +[![Documentation Status](https://readthedocs.org/projects/qudi-core-testing/badge/?version=latest)](https://qudi-core-testing.readthedocs.io/en/latest/?badge=latest) + --- The qudi-core repository represents the base installation for the `qudi` Python package. From 109b4e22e1dc48812f4aa02a26b92288e3f96244 Mon Sep 17 00:00:00 2001 From: George-Elhamy <104439178+George-Elhamy@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:32:15 +0200 Subject: [PATCH 10/10] API reference --- docs/index.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index fb162942b..6f5774b7e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,14 @@ Welcome to Qudi Core's documentation! :template: custom-module-template.rst :recursive: - qudi + qudi.core + qudi.gui + qudi.logic + qudi.tasks + qudi.tools + qudi.util + qudi.runnable + Indices and tables ==================