diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml new file mode 100644 index 0000000..0480091 --- /dev/null +++ b/.github/workflows/test_and_deploy.yml @@ -0,0 +1,90 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: tests + +on: + push: + branches: + - main + - npe2 + tags: + - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 + pull_request: + branches: + - main + - npe2 + workflow_dispatch: + +jobs: + test: + name: ${{ matrix.platform }} py${{ matrix.python-version }} + runs-on: ${{ matrix.platform }} + strategy: + matrix: + platform: [ubuntu-latest, windows-latest, macos-latest] + python-version: ['3.8', '3.9', '3.10'] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + # these libraries enable testing on Qt on linux + - uses: tlambert03/setup-qt-libs@v1 + + # strategy borrowed from vispy for installing opengl libs on windows + - name: Install Windows OpenGL + if: runner.os == 'Windows' + run: | + git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git + powershell gl-ci-helpers/appveyor/install_opengl.ps1 + + # note: if you need dependencies from conda, considering using + # setup-miniconda: https://github.com/conda-incubator/setup-miniconda + # and + # tox-conda: https://github.com/tox-dev/tox-conda + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install setuptools tox tox-gh-actions + + # this runs the platform-specific tests declared in tox.ini + - name: Test with tox + uses: aganders3/headless-gui@v1 + with: + run: python -m tox + env: + PLATFORM: ${{ matrix.platform }} + + - name: Coverage + uses: codecov/codecov-action@v3 + + deploy: + # this will run when you have tagged a commit, starting with "v*" + # and requires that you have put your twine API key in your + # github secrets (see readme for details) + needs: [test] + runs-on: ubuntu-latest + if: contains(github.ref, 'tags') + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -U setuptools setuptools_scm wheel twine build + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }} + run: | + git tag + python -m build . + twine upload dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2688328 --- /dev/null +++ b/.gitignore @@ -0,0 +1,84 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +.imswitch_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask instance folder +instance/ + +# Sphinx documentation +docs/_build/ + +# MkDocs documentation +/site/ + +# PyBuilder +target/ + +# Pycharm and VSCode +.idea/ +venv/ +.vscode/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# OS +.DS_Store + +# written by setuptools_scm +**/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ce6cc96 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: check-docstring-first + - id: end-of-file-fixer + - id: trailing-whitespace + exclude: ^\.napari-hub/.* + - id: check-yaml # checks for correct yaml syntax for github actions ex. + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.256 + hooks: + - id: ruff + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/tlambert03/napari-plugin-checks + rev: v0.3.0 + hooks: + - id: napari-plugin-checks + # https://mypy.readthedocs.io/en/stable/introduction.html + # you may wish to add this as well! + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v0.910-1 + # hooks: + # - id: mypy diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdc75bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2024 Benedict + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f3155af --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include LICENSE +include README.md + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fb1106 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# imswitch-arkitekt + +[![License MIT](https://img.shields.io/pypi/l/imswitch-arkitekt.svg?color=green)](https://github.com/beniroquai/imswitch-arkitekt/raw/main/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/imswitch-arkitekt.svg?color=green)](https://pypi.org/project/imswitch-arkitekt) +[![Python Version](https://img.shields.io/pypi/pyversions/imswitch-arkitekt.svg?color=green)](https://python.org) +[![tests](https://github.com/beniroquai/imswitch-arkitekt/workflows/tests/badge.svg)](https://github.com/beniroquai/imswitch-arkitekt/actions) +[![codecov](https://codecov.io/gh/beniroquai/imswitch-arkitekt/branch/main/graph/badge.svg)](https://codecov.io/gh/beniroquai/imswitch-arkitekt) +[![napari hub](https://img.shields.io/endpoint?url=https://api.napari-hub.org/shields/imswitch-arkitekt)](https://napari-hub.org/plugins/imswitch-arkitekt) + +This module will connect ImSwitch to Arkitekt + +---------------------------------- + +This [napari] plugin was generated with [Cookiecutter] using [@napari]'s [cookiecutter-napari-plugin] template. + + + +## Installation + +You can install `imswitch-arkitekt` via [pip]: + + pip install imswitch-arkitekt + + + + +## Contributing + +Contributions are very welcome. Tests can be run with [tox], please ensure +the coverage at least stays the same before you submit a pull request. + +## License + +Distributed under the terms of the [MIT] license, +"imswitch-arkitekt" is free and open source software + +## Issues + +If you encounter any problems, please [file an issue] along with a detailed description. + +[napari]: https://github.com/napari/napari +[Cookiecutter]: https://github.com/audreyr/cookiecutter +[@napari]: https://github.com/napari +[MIT]: http://opensource.org/licenses/MIT +[BSD-3]: http://opensource.org/licenses/BSD-3-Clause +[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt +[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt +[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0 +[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt +[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin + +[napari]: https://github.com/napari/napari +[tox]: https://tox.readthedocs.io/en/latest/ +[pip]: https://pypi.org/project/pip/ +[PyPI]: https://pypi.org/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..422dc2d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[build-system] +requires = ["setuptools>=42.0.0", "wheel"] +build-backend = "setuptools.build_meta" + + + +[tool.black] +line-length = 79 +target-version = ['py38', 'py39', 'py310'] + + +[tool.ruff] +line-length = 79 +select = [ + "E", "F", "W", #flake8 + "UP", # pyupgrade + "I", # isort + "BLE", # flake8-blind-exception + "B", # flake8-bugbear + "A", # flake8-builtins + "C4", # flake8-comprehensions + "ISC", # flake8-implicit-str-concat + "G", # flake8-logging-format + "PIE", # flake8-pie + "SIM", # flake8-simplify +] +ignore = [ + "E501", # line too long. let black handle this + "UP006", "UP007", # type annotation. As using magicgui require runtime type annotation then we disable this. + "SIM117", # flake8-simplify - some of merged with statements are not looking great with black, reanble after drop python 3.9 +] + +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".mypy_cache", + ".pants.d", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + "*vendored*", + "*_vendor*", +] + +target-version = "py38" +fix = true diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..c8c8b5f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,63 @@ +[metadata] +name = imswitch-arkitekt +version = attr: imswitch_arkitekt.__version__ +description = This module will connect ImSwitch to Arkitekt +long_description = file: README.md +long_description_content_type = text/markdown + +author = Benedict +author_email = bene.d@gmx.de +license = MIT +license_files = LICENSE +classifiers = + Development Status :: 2 - Pre-Alpha + Framework :: imswitch + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Topic :: Scientific/Engineering :: Image Processing + + +[options] +packages = find: +install_requires = + numpy + magicgui + qtpy + scikit-image + +python_requires = >=3.8 +include_package_data = True +package_dir = + =src + +# add your package requirements here + +[options.packages.find] +where = src + +[options.entry_points] +imswitch.manifest = + imswitch-arkitekt = imswitch_arkitekt:imswitch.yaml +imswitch.implugins = + imswitch_arkitekt_controller = imswitch_arkitekt:imswitch_arkitekt_controller + imswitch_arkitekt_widget = imswitch_arkitekt:imswitch_arkitekt_widget + +[options.extras_require] +testing = + tox + pytest # https://docs.pytest.org/en/latest/contents.html + pytest-cov # https://pytest-cov.readthedocs.io/en/latest/ + pytest-qt # https://pytest-qt.readthedocs.io/en/latest/ + imswitch + pyqt5 + + +[options.package_data] +* = *.yaml diff --git a/src/imswitch_arkitekt/__init__.py b/src/imswitch_arkitekt/__init__.py new file mode 100644 index 0000000..aa75814 --- /dev/null +++ b/src/imswitch_arkitekt/__init__.py @@ -0,0 +1,9 @@ +__version__ = "0.0.1" + +from .imswitch_arkitekt_controller import * +from .imswitch_arkitekt_widget import * +from .imswitch_arkitekt_manager import * +from .imswitch_arkitekt_info import * + +__all__ = ( +) diff --git a/src/imswitch_arkitekt/_tests/__init__.py b/src/imswitch_arkitekt/_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/imswitch_arkitekt/_tests/test_reader.py b/src/imswitch_arkitekt/_tests/test_reader.py new file mode 100644 index 0000000..b7b8569 --- /dev/null +++ b/src/imswitch_arkitekt/_tests/test_reader.py @@ -0,0 +1,31 @@ +import numpy as np + +from imswitch_arkitekt import imswitch_get_controller + + +# tmp_path is a pytest fixture +def test_controller(tmp_path): + """An example of how you might test your plugin.""" + + # write some fake data using your supported file format + my_test_file = str(tmp_path / "myfile.npy") + original_data = np.random.rand(20, 20) + np.save(my_test_file, original_data) + + # try to read it back in + reader = imswitch_get_controller(my_test_file) + assert callable(reader) + + # make sure we're delivering the right format + layer_data_list = reader(my_test_file) + assert isinstance(layer_data_list, list) and len(layer_data_list) > 0 + layer_data_tuple = layer_data_list[0] + assert isinstance(layer_data_tuple, tuple) and len(layer_data_tuple) > 0 + + # make sure it's the same as it started + np.testing.assert_allclose(original_data, layer_data_tuple[0]) + + +def test_get_controller_pass(): + reader = imswitch_get_controller("fake.file") + assert reader is None diff --git a/src/imswitch_arkitekt/_tests/test_sample_data.py b/src/imswitch_arkitekt/_tests/test_sample_data.py new file mode 100644 index 0000000..a8ffe9a --- /dev/null +++ b/src/imswitch_arkitekt/_tests/test_sample_data.py @@ -0,0 +1,7 @@ +# from imswitch_arkitekt import make_sample_data + +# add your tests here... + + +def test_something(): + pass diff --git a/src/imswitch_arkitekt/_tests/test_widget.py b/src/imswitch_arkitekt/_tests/test_widget.py new file mode 100644 index 0000000..518052b --- /dev/null +++ b/src/imswitch_arkitekt/_tests/test_widget.py @@ -0,0 +1,66 @@ +import numpy as np + +from imswitch_arkitekt._widget import ( + ExampleQWidget, + ImageThreshold, + threshold_autogenerate_widget, + threshold_magic_widget, +) + + +def test_threshold_autogenerate_widget(): + # because our "widget" is a pure function, we can call it and + # test it independently of imswitch + im_data = np.random.random((100, 100)) + thresholded = threshold_autogenerate_widget(im_data, 0.5) + assert thresholded.shape == im_data.shape + # etc. + + +# make_imswitch_viewer is a pytest fixture that returns a imswitch viewer object +# you don't need to import it, as long as imswitch is installed +# in your testing environment +def test_threshold_magic_widget(make_imswitch_viewer): + viewer = make_imswitch_viewer() + layer = viewer.add_image(np.random.random((100, 100))) + + # our widget will be a MagicFactory or FunctionGui instance + my_widget = threshold_magic_widget() + + # if we "call" this object, it'll execute our function + thresholded = my_widget(viewer.layers[0], 0.5) + assert thresholded.shape == layer.data.shape + # etc. + + +def test_image_threshold_widget(make_imswitch_viewer): + viewer = make_imswitch_viewer() + layer = viewer.add_image(np.random.random((100, 100))) + my_widget = ImageThreshold(viewer) + + # because we saved our widgets as attributes of the container + # we can set their values without having to "interact" with the viewer + my_widget._image_layer_combo.value = layer + my_widget._threshold_slider.value = 0.5 + + # this allows us to run our functions directly and ensure + # correct results + my_widget._threshold_im() + assert len(viewer.layers) == 2 + + +# capsys is a pytest fixture that captures stdout and stderr output streams +def test_example_q_widget(make_imswitch_viewer, capsys): + # make viewer and add an image layer using our fixture + viewer = make_imswitch_viewer() + viewer.add_image(np.random.random((100, 100))) + + # create our widget, passing in the viewer + my_widget = ExampleQWidget(viewer) + + # call our widget method + my_widget._on_click() + + # read captured output and check that it's as we expected + captured = capsys.readouterr() + assert captured.out == "imswitch has 1 layers\n" diff --git a/src/imswitch_arkitekt/_tests/test_writer.py b/src/imswitch_arkitekt/_tests/test_writer.py new file mode 100644 index 0000000..211ab3f --- /dev/null +++ b/src/imswitch_arkitekt/_tests/test_writer.py @@ -0,0 +1,7 @@ +# from imswitch_arkitekt import write_single_image, write_multiple + +# add your tests here... + + +def test_something(): + pass diff --git a/src/imswitch_arkitekt/imswitch.yaml b/src/imswitch_arkitekt/imswitch.yaml new file mode 100644 index 0000000..8eac58f --- /dev/null +++ b/src/imswitch_arkitekt/imswitch.yaml @@ -0,0 +1,54 @@ +name: imswitch-arkitekt +display_name: ImSwitch Arkitekt +# use 'hidden' to remove plugin from imswitch hub search results +visibility: public +# see https://imswitch.org/stable/plugins/manifest.html for valid categories +categories: ["Annotation", "Segmentation", "Acquisition"] +contributions: + commands: + - id: imswitch-arkitekt.get_controller + python_name: imswitch_arkitekt._controller:imswitch_get_controller + title: Open data with ImSwitch Arkitekt + - id: imswitch-arkitekt.write_multiple + python_name: imswitch_arkitekt._manager:write_multiple + title: Save multi-layer data with ImSwitch Arkitekt + - id: imswitch-arkitekt.make_sample_data + python_name: imswitch_arkitekt._sample_data:make_sample_data + title: Load sample data from ImSwitch Arkitekt + - id: imswitch-arkitekt.make_container_widget + python_name: imswitch_arkitekt:ImageThreshold + title: Make threshold Container widget + - id: imswitch-arkitekt.make_magic_widget + python_name: imswitch_arkitekt:threshold_magic_widget + title: Make threshold magic widget + - id: imswitch-arkitekt.make_function_widget + python_name: imswitch_arkitekt:threshold_autogenerate_widget + title: Make threshold function widget + - id: imswitch-arkitekt.make_qwidget + python_name: imswitch_arkitekt:ExampleQWidget + title: Make example QWidget + controllers: + - command: imswitch-arkitekt.get_controller + accepts_directories: false + filename_patterns: ['*.npy'] + info: + - command: imswitch-arkitekt.get_info + accepts_directories: false + filename_patterns: ['*.npy'] + managers: + - command: imswitch-arkitekt.write_multiple + layer_types: ['image*','labels*'] + filename_extensions: [] + - command: imswitch-arkitekt.write_single_image + layer_types: ['image'] + key: unique_id.1 + widgets: + - command: imswitch-arkitekt.make_container_widget + display_name: Container Threshold + - command: imswitch-arkitekt.make_magic_widget + display_name: Magic Threshold + - command: imswitch-arkitekt.make_function_widget + autogenerate: true + display_name: Autogenerate Threshold + - command: imswitch-arkitekt.make_qwidget + display_name: Example QWidget diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_controller.py b/src/imswitch_arkitekt/imswitch_arkitekt_controller.py new file mode 100644 index 0000000..34c35d1 --- /dev/null +++ b/src/imswitch_arkitekt/imswitch_arkitekt_controller.py @@ -0,0 +1,13 @@ +from imswitch.imcontrol.model.managers.detectors.DetectorManager import DetectorManager, DetectorAction, DetectorNumberParameter +from imswitch.imcontrol.controller.basecontrollers import ImConWidgetController +from imswitch.imcontrol.view.ImConMainView import _DockInfo + +import numpy as np +from typing import Any, Dict, List, Optional, Tuple + + + +class imswitch_arkitekt_controller(ImConWidgetController): + """Linked to CameraPluginWidget.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_info.py b/src/imswitch_arkitekt/imswitch_arkitekt_info.py new file mode 100644 index 0000000..2774e89 --- /dev/null +++ b/src/imswitch_arkitekt/imswitch_arkitekt_info.py @@ -0,0 +1,33 @@ +from dataclasses import dataclass + +@dataclass(frozen=True) +class imswitch_arkitekt_info: + monitorIdx: int + """ Index of the monitor in the system list of monitors (indexing starts at + 0). """ + + width: int + """ Width of SLM, in pixels. """ + + height: int + """ Height of SLM, in pixels. """ + + wavelength: int + """ Wavelength of the laser line used with the SLM. """ + + pixelSize: float + """ Pixel size or pixel pitch of the SLM, in millimetres. """ + + angleMount: float + """ The angle of incidence and reflection of the laser line that is shaped + by the SLM, in radians. For adding a blazed grating to create off-axis + holography. """ + + patternsDir: str + """ Directory of .bmp images provided by Hamamatsu for flatness correction + at various wavelengths. A combination will be chosen based on the + wavelength. """ + + + + diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_manager.py b/src/imswitch_arkitekt/imswitch_arkitekt_manager.py new file mode 100644 index 0000000..b839ec4 --- /dev/null +++ b/src/imswitch_arkitekt/imswitch_arkitekt_manager.py @@ -0,0 +1,32 @@ + +from imswitch.imcontrol.view.guitools.ViewSetupInfo import ViewSetupInfo as SetupInfo +from imswitch.imcommon.framework import Signal, SignalInterface +from imswitch.imcommon.model import initLogger + + +class imswitch_arkitekt_manager(SignalInterface): + sigSIMMaskUpdated = Signal(object) # (maskCombined) + + def __init__(self, pluginInfo, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__logger = initLogger(self) + + if pluginInfo is None: + # import imswitch_sim_info.py + return + + self.__pluginInfo = pluginInfo + self.__wavelength = self.__pluginInfo.wavelength + self.__pixelsize = self.__pluginInfo.pixelSize + self.__angleMount = self.__pluginInfo.angleMount + self.__simSize = (self.__pluginInfo.width, self.__pluginInfo.height) + self.__patternsDir = self.__pluginInfo.patternsDir + self.isSimulation = self.__pluginInfo.isSimulation + self.nRotations = self.__pluginInfo.nRotations + self.nPhases = self.__pluginInfo.nPhases + self.simMagnefication = self.__pluginInfo.nPhases + self.isFastAPISIM = self.__pluginInfo.isFastAPISIM + self.simPixelsize = self.__pluginInfo.simPixelsize + self.simNA = self.__pluginInfo.simNA + self.simN = self.__pluginInfo.simN # refr + self.simETA = self.__pluginInfo.simETA diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_widget.py b/src/imswitch_arkitekt/imswitch_arkitekt_widget.py new file mode 100644 index 0000000..a184068 --- /dev/null +++ b/src/imswitch_arkitekt/imswitch_arkitekt_widget.py @@ -0,0 +1,14 @@ +from imswitch.imcontrol.view.widgets.basewidgets import Widget +import numpy as np +from typing import Any, Dict, List, Optional, Tuple + +class dotdict(dict): + """dot.notation access to dictionary attributes""" + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + +class imswitch_arkitekt_widget(Widget): + """Linked to CameraPluginWidget.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2e121bc --- /dev/null +++ b/tox.ini @@ -0,0 +1,32 @@ +# For more information about tox, see https://tox.readthedocs.io/en/latest/ +[tox] +envlist = py{38,39,310}-{linux,macos,windows} +isolated_build=true + +[gh-actions] +python = + 3.8: py38 + 3.9: py39 + 3.10: py310 + +[gh-actions:env] +PLATFORM = + ubuntu-latest: linux + macos-latest: macos + windows-latest: windows + +[testenv] +platform = + macos: darwin + linux: linux + windows: win32 +passenv = + CI + GITHUB_ACTIONS + DISPLAY + XAUTHORITY + NUMPY_EXPERIMENTAL_ARRAY_FUNCTION + PYVISTA_OFF_SCREEN +extras = + testing +commands = pytest -v --color=yes --cov=imswitch_arkitekt --cov-report=xml