├── .conda-recipe └── meta.yaml ├── .gitattributes ├── .gitignore ├── .miniconda-recipe ├── EULA.txt ├── construct.yaml ├── logo.png └── post-install.bat ├── CHANGELOG ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── SConstruct ├── appveyor.yml ├── docs ├── Makefile ├── conf.py ├── generate_modules.py ├── index.rst ├── make.bat ├── microdrop.bin.rst ├── microdrop.core_plugins.device_info_plugin.rst ├── microdrop.core_plugins.electrode_controller_plugin.rst ├── microdrop.core_plugins.rst ├── microdrop.core_plugins.zmq_hub_plugin.rst ├── microdrop.gui.rst ├── microdrop.rst ├── microdrop.tests.rst ├── modules.rst └── rename.py ├── microdrop ├── __init__.py ├── __main__.py ├── _version.py ├── app.py ├── app_context.py ├── bin │ ├── __init__.py │ ├── config.py │ └── create_portable_config.py ├── config.py ├── core_plugins │ ├── __init__.py │ ├── command_plugin │ │ ├── __init__.py │ │ ├── microdrop_plugin.py │ │ └── plugin.py │ ├── device_info_plugin │ │ └── __init__.py │ ├── electrode_controller_plugin │ │ ├── __init__.py │ │ ├── execute.py │ │ └── pyutilib.py │ ├── prompt_plugin │ │ └── __init__.py │ ├── protocol_controller │ │ ├── __init__.py │ │ └── execute.py │ └── zmq_hub_plugin │ │ └── __init__.py ├── devices │ ├── DMF-90-pin-array │ │ └── device.svg │ ├── SCI-BOTS 90-pin array │ │ └── device.svg │ └── pin map │ │ └── device.svg ├── dmf_device.py ├── experiment_log.py ├── gui │ ├── __init__.py │ ├── app_options_controller.py │ ├── cairo_view.py │ ├── channel_sweep.py │ ├── config_controller.py │ ├── dmf_device_controller.py │ ├── experiment_log_controller.py │ ├── field_filter_controller.py │ ├── glade │ │ ├── about_dialog.glade │ │ ├── app_options_dialog.glade │ │ ├── configuration_dialog.glade │ │ ├── dmf_device_view.glade │ │ ├── dmf_device_view_context_menu.glade │ │ ├── main_window.glade │ │ ├── plugin_download_dialog.glade │ │ ├── plugin_manager_dialog.glade │ │ └── text_input_dialog.glade │ ├── main_window_controller.py │ ├── plugin_manager_controller.py │ ├── plugin_manager_dialog.py │ └── protocol_grid_controller.py ├── interfaces.py ├── logger.py ├── microdrop.ico ├── microdrop.py ├── plugin_helpers.py ├── plugin_manager.py ├── protocol.py ├── static │ ├── icon.svg │ ├── images │ │ ├── microdrop-environment-shortcut.png │ │ ├── microdrop-plugin-manager-annotated.png │ │ ├── microdrop-shortcuts.png │ │ ├── plugin-manager-install-dependencies.png │ │ ├── plugin-manager-remove.png │ │ └── plugins-install.gif │ └── notebooks │ │ ├── Analyze (merged) 120 channel test board impedance sweeps.ipynb │ │ ├── Analyze merged channel impedance sweeps.ipynb │ │ ├── Assign chip channels.ipynb │ │ ├── Experiment log explorer.ipynb │ │ └── dump feedback results to csv.ipynb ├── support │ └── gen_py │ │ └── __init__.py └── tests │ ├── __init__.py │ ├── buildbot │ └── master.cfg │ ├── devices │ ├── device 0 v0.2.0 │ ├── device 0 v0.3.0 │ ├── device 1 v0.2.0 │ └── device 1 v0.3.0 │ ├── experiment_logs │ ├── experiment log 0 v0.0.0 │ └── experiment log 0 v0.1.0 │ ├── protocols │ ├── protocol 0 v0.0.0 │ └── protocol 0 v0.1.0 │ ├── svg_files │ ├── test_device_0.svg │ ├── test_device_1.svg │ ├── test_device_2.svg │ ├── test_device_3.svg │ ├── test_device_4.svg │ └── test_device_5.svg │ ├── test_dmf_device.py │ ├── test_experiment_log.py │ ├── test_protocol.py │ └── update_dmf_control_board.py ├── pavement.py ├── requirements-sphinx.txt ├── requirements.txt ├── setup.cfg ├── setup_requirements.txt ├── site_scons ├── __init__.py ├── git_util.py └── path_find.py └── versioneer.py /.conda-recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% if GIT_DESCRIBE_NUMBER > '0' %} 2 | {% set GIT_VERSION = GIT_DESCRIBE_TAG[1:] + '.post' + GIT_DESCRIBE_NUMBER %} 3 | {% else %} 4 | {% set GIT_VERSION = GIT_DESCRIBE_TAG[1:] %} 5 | {% endif %} 6 | # source will be downloaded prior to filling in jinja templates 7 | # Example assumes that this folder has setup.py in it 8 | source: 9 | git_url: ../ 10 | 11 | package: 12 | name: microdrop 13 | version: {{ GIT_VERSION }} 14 | 15 | build: 16 | entry_points: 17 | - microdrop = microdrop.microdrop:main 18 | # .. versionadded:: 2.13 19 | - microdrop-config = microdrop.bin.config:main 20 | skip: True # [py>27 or not win] 21 | number: 0 22 | script: 23 | # Generate `setup.py` from `pavement.py` definition. 24 | - python -m paver generate_setup 25 | - python -m pip install --no-deps --ignore-installed -vv . 26 | 27 | requirements: 28 | build: 29 | - python 30 | - pip 31 | - paver 32 | 33 | run: 34 | - application-repository 35 | - arrow 36 | #: ..versionadded:: 2.30 37 | - markdown2pango 38 | #: ..versionadded:: 2.25 39 | - asyncio-helpers >=0.2 40 | - blinker 41 | - configobj 42 | #: ..versionadded:: 2.16 43 | - debounce 44 | - droplet-planning 45 | #: .. versionadded:: 2.15.2 46 | - flatland-fork >=0.5 47 | - functools32 48 | - geo_util 49 | - git_helpers 50 | - gitpython 51 | - ipython 52 | - jupyter-helpers 53 | - jinja2 54 | - logging-helpers >=0.2 55 | - lxml 56 | - matplotlib 57 | - microdrop-device-converter >=0.1.post5 58 | #: .. versionchanged:: 2.16.1 59 | - microdrop-plugin-manager >=0.25.1 60 | #: .. versionadded:: 2.15.1 61 | - microdrop-plugin-template >=1.3 62 | - networkx 63 | - openpyxl 64 | - pandas 65 | #: .. versionchanged:: 2.21 66 | - path_helpers >=0.8 67 | - paver 68 | - pint 69 | - pip-helpers 70 | - pycairo-gtk2 71 | #: .. versionadded:: 2.13 72 | - pydash 73 | - pygtk-textbuffer-with-undo 74 | - pymunk >=4.0,<5.0 75 | - pyparsing 76 | - pyserial 77 | - pytables 78 | - pyutilib.component.loader 79 | - pywin32 80 | - pyyaml 81 | - pyzmq 82 | - run-exe 83 | - scipy 84 | - setuptools 85 | - si-prefix 86 | - svg-model 87 | - svgwrite 88 | - sympy 89 | - task-scheduler 90 | - tornado 91 | #: .. versionchanged:: 2.25.1 92 | #: Ignore callback return value in `gtk_threadsafe()` decorator to 93 | #: avoid calling functions returning `True` repeatedly and 94 | #: indefinitely. 95 | - wheeler.pygtkhelpers >=0.22 96 | #: .. versionchanged:: 2.25 97 | - zmq-plugin >=0.3.3 98 | 99 | about: 100 | home: https://github.com/sci-bots/microdrop 101 | license: BSD 102 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/docket/_version.py export-subst 2 | microdrop/_version.py export-subst 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | download_cache 2 | logdict* 3 | microdrop/version.txt 4 | microdrop/etc/ 5 | microdrop/share/ 6 | doc 7 | build 8 | dist 9 | *.pyc 10 | .sconsign.dblite 11 | dmf_control_board_base.so 12 | .microdrop 13 | *.exe 14 | *.dll 15 | *.pyd 16 | .project 17 | .pydevproject 18 | warnmicrodrop.txt 19 | *.msi 20 | *.wixpdb 21 | *.wixobj 22 | *.wxs 23 | microdrop/plugins/*.yml 24 | .settings/ 25 | src/ 26 | setup.py 27 | paver-minilib.zip 28 | *.egg-info/ 29 | microdrop/requirements.txt 30 | .idea/ 31 | docs/_* 32 | 33 | # Back-up files. 34 | *~ 35 | 36 | # Old submodules. Now separate Python packages. 37 | microdrop/gui/textbuffer_with_undo/ 38 | microdrop/svg_model/ 39 | microdrop/task_scheduler/ 40 | microdrop/update_repository/ 41 | microdrop/utility/ 42 | -------------------------------------------------------------------------------- /.miniconda-recipe/EULA.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Sci-Bots Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.miniconda-recipe/construct.yaml: -------------------------------------------------------------------------------- 1 | name: MicroDrop 2 | version: {{ GIT_DESCRIBE_TAG[1:] }} 3 | 4 | channels: 5 | - https://conda.anaconda.org/microdrop-plugins 6 | - https://conda.anaconda.org/sci-bots 7 | - https://conda.anaconda.org/wheeler-microfluidics/ 8 | - https://conda.anaconda.org/conda-forge/ 9 | - http://repo.continuum.io/pkgs/free/ 10 | 11 | specs: 12 | - conda 13 | #: .. versionchanged:: 2.18 14 | #: .. versionchanged:: 2.19 15 | - dmf-device-ui >=0.10 16 | - microdrop =={{ GIT_DESCRIBE_TAG[1:] }} 17 | #: .. versionchanged:: 2.15 18 | #: .. versionchanged:: 2.18.1 19 | - microdrop-launcher >=0.7.12 20 | #: .. versionadded:: 2.15 21 | #: .. versionchanged:: 2.18 22 | - microdrop-plugin-manager >=0.25.1 23 | - pip 24 | - python 2.7* 25 | - pywin32 26 | # Install MicroDrop plugins 27 | #: .. versionchanged:: 2.18 28 | - microdrop.droplet-planning-plugin >=2.3.1 29 | #: .. versionchanged:: 2.18 30 | - microdrop.dmf-device-ui-plugin >=2.6 31 | #: .. versionchanged:: 2.15 32 | #: .. versionchanged:: 2.18 33 | #: .. versionchanged:: 2.18.3 34 | - microdrop.dropbot-plugin >=2.22.5 35 | #: .. versionchanged:: 2.18 36 | - microdrop.user-prompt-plugin >=2.3.1 37 | #: .. versionchanged:: 2.18 38 | - microdrop.step-label-plugin >=2.2.2 39 | 40 | license_file: EULA.txt 41 | 42 | post_install: post-install.bat [win] 43 | 44 | # Welcome image for Windows installer 45 | welcome_image: logo.png [win] 46 | # Write `.condarc` including default channels 47 | #: .. versionadded:: 2.15 48 | write_condarc: True 49 | #: .. versionadded:: 2.15 50 | install_in_dependency_order: True 51 | #: .. versionadded:: 2.15.3 52 | ignore_duplicate_files: True 53 | #: .. versionadded:: 2.15 54 | # By default, do not add the installation to the PATH environment variable. 55 | # The user is still able to change this during interactive installation. 56 | add_to_path_default: False 57 | #: .. versionadded:: 2.15 58 | # By default, do not register the installed Python instance as the system's 59 | # default Python. 60 | # The user is still able to change this during interactive installation. 61 | register_python_default: False [win] 62 | -------------------------------------------------------------------------------- /.miniconda-recipe/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/.miniconda-recipe/logo.png -------------------------------------------------------------------------------- /.miniconda-recipe/post-install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Explicitly move noarch packages into `Lib/site-packages` as a workaround to 3 | REM [this issue][i86] with lack of `constructor` support for `noarch` packages. 4 | REM 5 | REM [i86]: https://github.com/conda/constructor/issues/86#issuecomment-330863531 6 | IF EXIST site-packages ( 7 | REM Move directory modules. 8 | for /D %%i in (site-packages/*) do IF "%%~xi" == "" (IF NOT EXIST Lib\site-packages\%%i ( 9 | echo Move noarch package: %%i 10 | move site-packages\%%i Lib\site-packages 11 | )) 12 | REM Move file modules. 13 | for %%i in (site-packages/*.py) do IF "%%~xi" == ".py" (IF NOT EXIST Lib\site-packages\%%i ( 14 | echo Move noarch package: %%i 15 | move site-packages\%%i Lib\site-packages 16 | )) 17 | rmdir /S/Q site-packages 18 | ) 19 | 20 | @echo on 21 | REM Link installed plugins into Conda MicroDrop activated plugins directory. 22 | call Scripts\activate.bat %CD% & python -m mpm.bin.api enable droplet_planning_plugin dmf_device_ui_plugin dropbot_plugin user_prompt_plugin step_label_plugin 23 | 24 | REM Load plugins by default 25 | call Scripts\activate.bat %CD% & microdrop-config edit --append plugins.enabled droplet_planning_plugin 26 | call Scripts\activate.bat %CD% & microdrop-config edit --append plugins.enabled dmf_device_ui_plugin 27 | call Scripts\activate.bat %CD% & microdrop-config edit --append plugins.enabled dropbot_plugin 28 | call Scripts\activate.bat %CD% & microdrop-config edit --append plugins.enabled user_prompt_plugin 29 | call Scripts\activate.bat %CD% & microdrop-config edit --append plugins.enabled step_label_plugin 30 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | = Version 1.0.18 = 2 | * Add IPython notebook integration 3 | 4 | = Version 1.0 = 5 | * Allow packaging as portable application (i.e., a self-contained folder including all data and config files) which will enable multiple versions of Microdrop to be installed on a system simultaneously 6 | * Establish a convention for plugin updating (major version of plugins must match the major version of Microdrop) 7 | * Add `on_plugin_install` hook to plugins (triggered on subsequent program launch). This may be used, for example, to install dependencies. 8 | * Log serial number, hardware version and software versions of all hardware devices to the experiment log 9 | * Log all enabled plugins and their versions to the experiment log 10 | * Turn off realtime mode on startup 11 | * Allow loading of protocols even if plugins used in the protocol are not available 12 | * Add `data_dir` setting to config file 13 | * Add command-line argument to set config file path 14 | * Other bug fixes 15 | 16 | = Version 0.1.706 = 17 | * Start new experiment log when user presses play 18 | * Bug fixes 19 | 20 | = Version 0.1.701 = 21 | * Allow resizing of main window 22 | * Bug fixes 23 | 24 | = Version 0.1.696 = 25 | * Add video overlay mode 26 | * Bug fixes 27 | 28 | = Version 0.1.645 = 29 | * GUI remains responsive when running a protocol 30 | * Gracefully fail when installing bad plugin 31 | * Bug fixes 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Sci-Bots Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.md 3 | include CHANGELOG 4 | include setup.py 5 | include paver-minilib.zip 6 | recursive-include microdrop * 7 | prune *.pyc 8 | include versioneer.py 9 | include microdrop/_version.py 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to MicroDrop # 2 | 3 | [![Documentation Status](https://readthedocs.org/projects/microdrop/badge/?version=dev)][doc-status] 4 | [![Join the chat at https://gitter.im/wheeler-microfluidics/microdrop](https://badges.gitter.im/wheeler-microfluidics/microdrop.svg)][gitter] 5 | 6 | 7 | 8 | MicroDrop is a graphical user interface for the [DropBot][0] Digital 9 | Microfluidics control system (described in detail in ["Fobel et al., Appl. 10 | Phys. Lett. 102, 193513 (2013); doi: 10.1063/1.4807118"][dropbot-doi]). If you 11 | use this information in work that you publish, please cite as appropriate. 12 | 13 | Take a look at some [screenshots][screenshots] or [videos][videos] of the 14 | software in action. 15 | 16 | ## Getting started 17 | 18 | Check out the **[Quick start guide][quick-start]** for detailed instructions on 19 | downloading and installing MicroDrop. For further instructions on using the 20 | software, please see the **[User guide][user-guide].** 21 | 22 | ## Contributing 23 | 24 | All source-code code is open-source and released under the [3-clause BSD 25 | License][1]. 26 | 27 | If you would like to contribute to this software, please check out the 28 | **[Developer guide][2]**. To see what we're working on and what's planned for 29 | future versions of MicroDrop, take a look at the **[Development roadmap][3]**. 30 | 31 | ## Troubleshooting 32 | 33 | If you're having problems, see the **[Troubleshooting guide][troubleshooting]** 34 | and check the [issue tracker][issues] for any known issues or to report new 35 | ones. 36 | 37 | ## Mailing lists 38 | 39 | Sign up for the [DropBot ​development mailing list][mailing-list] to 40 | report issues or ask questions related to DropBot and/or MicroDrop. For 41 | announcements concerning future releases/updates, join the [announcement 42 | list][announcements]. 43 | 44 | ## Credits ## 45 | 46 | Ryan Fobel 47 | Christian Fobel 48 | 49 | 50 | [doc-status]: http://microdrop.readthedocs.io/en/dev/?badge=dev 51 | [gitter]: https://gitter.im/wheeler-microfluidics/microdrop?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 52 | [dropbot-doi]: http://dx.doi.org/10.1063/1.4807118 53 | [screenshots]: https://github.com/sci-bots/microdrop/wiki/screenshots 54 | [videos]: https://www.youtube.com/playlist?list=PLIt-zp-hJBUE_vrqJAru6HLWtXAPULHTL 55 | [quick-start]: https://github.com/sci-bots/microdrop/wiki/Quick-Start-Guide 56 | [user-guide]: https://github.com/sci-bots/microdrop/wiki/User-Guide 57 | [troubleshooting]: https://github.com/sci-bots/microdrop/wiki/Troubleshooting-Guide 58 | [issues]: https://github.com/sci-bots/microdrop/issues 59 | [mailing-list]: https://groups.google.com/forum/#!forum/dropbot-dev 60 | [announcements]: https://groups.google.com/forum/#!forum/dropbot-announce 61 | [0]: http://microfluidics.utoronto.ca/dropbot 62 | [1]: https://github.com/sci-bots/microdrop/blob/master/LICENSE.md 63 | [2]: https://github.com/sci-bots/microdrop/wiki/Developer-Guide 64 | [3]: https://github.com/sci-bots/microdrop/wiki/Development-roadmap 65 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | env = Environment(ENV=os.environ) 4 | Export('env') 5 | 6 | ARGUMENTS['genrst'] = 'python sphinx-autopackage-script/generate_modules.py ../microdrop -d . -s rst -f' 7 | SConscript('doc/SConstruct') 8 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | GIT_REPOSITORY: https://github.com/wheeler-microfluidics/microdrop.git 3 | matrix: 4 | - PYTHON_VERSION: 2.7 5 | MINICONDA: C:\Miniconda 6 | PYTHON_ARCH: "32" 7 | 8 | version: '2.8.1_appveyor-{build}' 9 | 10 | init: 11 | - "ECHO %PYTHON_VERSION% %MINICONDA%" 12 | 13 | install: 14 | # Exit if no .conda-recipe folder 15 | - ps: if(!(Test-Path ".conda-recipe")){Add-AppveyorMessage -Message "No Conda Recipe Found" -Category "Error"; throw "Missing .conda-recipe folder"} 16 | - git clone --depth=1 https://github.com/sci-bots/sci-bots-configs.git 17 | - powershell -executionpolicy remotesigned -File .\sci-bots-configs\appveyor-install.ps1 18 | 19 | # Handle build and tests using conda (defined in .conda-recipe/meta.yaml) 20 | build: false 21 | test_script: 22 | - echo Build Complete 23 | 24 | after_test: 25 | - powershell -executionpolicy remotesigned -File .\sci-bots-configs\appveyor-after-test.ps1 -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/microdrop.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/microdrop.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/microdrop" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/microdrop" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. microdrop documentation master file, created by 2 | sphinx-quickstart on Mon Aug 22 12:39:50 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to microdrop's documentation! 7 | =========================================== 8 | 9 | .. include:: README.rst 10 | 11 | ----------------- 12 | 13 | Contents: 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | modules 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\microdrop.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\microdrop.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/microdrop.bin.rst: -------------------------------------------------------------------------------- 1 | bin Package 2 | =========== 3 | 4 | :mod:`create_portable_config` Module 5 | ------------------------------------ 6 | 7 | .. automodule:: microdrop.bin.create_portable_config 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`latest_versions` Module 13 | ----------------------------- 14 | 15 | .. automodule:: microdrop.bin.latest_versions 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/microdrop.core_plugins.device_info_plugin.rst: -------------------------------------------------------------------------------- 1 | device_info_plugin Package 2 | ========================== 3 | 4 | :mod:`device_info_plugin` Package 5 | --------------------------------- 6 | 7 | .. automodule:: microdrop.core_plugins.device_info_plugin 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`on_plugin_install` Module 13 | ------------------------------- 14 | 15 | .. automodule:: microdrop.core_plugins.device_info_plugin.on_plugin_install 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`release` Module 21 | --------------------- 22 | 23 | .. automodule:: microdrop.core_plugins.device_info_plugin.release 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`rename` Module 29 | -------------------- 30 | 31 | .. automodule:: microdrop.core_plugins.device_info_plugin.rename 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/microdrop.core_plugins.electrode_controller_plugin.rst: -------------------------------------------------------------------------------- 1 | electrode_controller_plugin Package 2 | =================================== 3 | 4 | :mod:`electrode_controller_plugin` Package 5 | ------------------------------------------ 6 | 7 | .. automodule:: microdrop.core_plugins.electrode_controller_plugin 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`on_plugin_install` Module 13 | ------------------------------- 14 | 15 | .. automodule:: microdrop.core_plugins.electrode_controller_plugin.on_plugin_install 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`release` Module 21 | --------------------- 22 | 23 | .. automodule:: microdrop.core_plugins.electrode_controller_plugin.release 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`rename` Module 29 | -------------------- 30 | 31 | .. automodule:: microdrop.core_plugins.electrode_controller_plugin.rename 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/microdrop.core_plugins.rst: -------------------------------------------------------------------------------- 1 | core_plugins Package 2 | ==================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | microdrop.core_plugins.device_info_plugin 10 | microdrop.core_plugins.electrode_controller_plugin 11 | microdrop.core_plugins.zmq_hub_plugin 12 | 13 | -------------------------------------------------------------------------------- /docs/microdrop.core_plugins.zmq_hub_plugin.rst: -------------------------------------------------------------------------------- 1 | zmq_hub_plugin Package 2 | ====================== 3 | 4 | :mod:`zmq_hub_plugin` Package 5 | ----------------------------- 6 | 7 | .. automodule:: microdrop.core_plugins.zmq_hub_plugin 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`on_plugin_install` Module 13 | ------------------------------- 14 | 15 | .. automodule:: microdrop.core_plugins.zmq_hub_plugin.on_plugin_install 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`release` Module 21 | --------------------- 22 | 23 | .. automodule:: microdrop.core_plugins.zmq_hub_plugin.release 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`rename` Module 29 | -------------------- 30 | 31 | .. automodule:: microdrop.core_plugins.zmq_hub_plugin.rename 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/microdrop.gui.rst: -------------------------------------------------------------------------------- 1 | gui Package 2 | =========== 3 | 4 | :mod:`app_options_controller` Module 5 | ------------------------------------ 6 | 7 | .. automodule:: microdrop.gui.app_options_controller 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`cairo_view` Module 13 | ------------------------ 14 | 15 | .. automodule:: microdrop.gui.cairo_view 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`channel_sweep` Module 21 | --------------------------- 22 | 23 | .. automodule:: microdrop.gui.channel_sweep 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`config_controller` Module 29 | ------------------------------- 30 | 31 | .. automodule:: microdrop.gui.config_controller 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`dmf_device_controller` Module 37 | ----------------------------------- 38 | 39 | .. automodule:: microdrop.gui.dmf_device_controller 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`dmf_device_controller.video` Module 45 | ----------------------------------------- 46 | 47 | .. automodule:: microdrop.gui.dmf_device_controller.video 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`dmf_device_view.video` Module 53 | ----------------------------------- 54 | 55 | .. automodule:: microdrop.gui.dmf_device_view.video 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`experiment_log_controller` Module 61 | --------------------------------------- 62 | 63 | .. automodule:: microdrop.gui.experiment_log_controller 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`field_filter_controller` Module 69 | ------------------------------------- 70 | 71 | .. automodule:: microdrop.gui.field_filter_controller 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | :mod:`main_window_controller` Module 77 | ------------------------------------ 78 | 79 | .. automodule:: microdrop.gui.main_window_controller 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | :mod:`plugin_download_dialog` Module 85 | ------------------------------------ 86 | 87 | .. automodule:: microdrop.gui.plugin_download_dialog 88 | :members: 89 | :undoc-members: 90 | :show-inheritance: 91 | 92 | :mod:`plugin_manager_controller` Module 93 | --------------------------------------- 94 | 95 | .. automodule:: microdrop.gui.plugin_manager_controller 96 | :members: 97 | :undoc-members: 98 | :show-inheritance: 99 | 100 | :mod:`plugin_manager_dialog` Module 101 | ----------------------------------- 102 | 103 | .. automodule:: microdrop.gui.plugin_manager_dialog 104 | :members: 105 | :undoc-members: 106 | :show-inheritance: 107 | 108 | :mod:`protocol_controller` Module 109 | --------------------------------- 110 | 111 | .. automodule:: microdrop.gui.protocol_controller 112 | :members: 113 | :undoc-members: 114 | :show-inheritance: 115 | 116 | :mod:`protocol_grid_controller` Module 117 | -------------------------------------- 118 | 119 | .. automodule:: microdrop.gui.protocol_grid_controller 120 | :members: 121 | :undoc-members: 122 | :show-inheritance: 123 | 124 | -------------------------------------------------------------------------------- /docs/microdrop.rst: -------------------------------------------------------------------------------- 1 | microdrop Package 2 | ================= 3 | 4 | :mod:`microdrop` Package 5 | ------------------------ 6 | 7 | .. automodule:: microdrop.__init__ 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`__main__` Module 13 | ---------------------- 14 | 15 | .. automodule:: microdrop.__main__ 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`app` Module 21 | ----------------- 22 | 23 | .. automodule:: microdrop.app 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`app_context` Module 29 | ------------------------- 30 | 31 | .. automodule:: microdrop.app_context 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`config` Module 37 | -------------------- 38 | 39 | .. automodule:: microdrop.config 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`dmf_device` Module 45 | ------------------------ 46 | 47 | .. automodule:: microdrop.dmf_device 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`experiment_log` Module 53 | ---------------------------- 54 | 55 | .. automodule:: microdrop.experiment_log 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`interfaces` Module 61 | ------------------------ 62 | 63 | .. automodule:: microdrop.interfaces 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`logger` Module 69 | -------------------- 70 | 71 | .. automodule:: microdrop.logger 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | :mod:`microdrop` Module 77 | ----------------------- 78 | 79 | .. automodule:: microdrop.microdrop 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | :mod:`plugin_helpers` Module 85 | ---------------------------- 86 | 87 | .. automodule:: microdrop.plugin_helpers 88 | :members: 89 | :undoc-members: 90 | :show-inheritance: 91 | 92 | :mod:`plugin_manager` Module 93 | ---------------------------- 94 | 95 | .. automodule:: microdrop.plugin_manager 96 | :members: 97 | :undoc-members: 98 | :show-inheritance: 99 | 100 | :mod:`protocol` Module 101 | ---------------------- 102 | 103 | .. automodule:: microdrop.protocol 104 | :members: 105 | :undoc-members: 106 | :show-inheritance: 107 | 108 | Subpackages 109 | ----------- 110 | 111 | .. toctree:: 112 | 113 | microdrop.bin 114 | microdrop.core_plugins 115 | microdrop.gui 116 | microdrop.tests 117 | 118 | -------------------------------------------------------------------------------- /docs/microdrop.tests.rst: -------------------------------------------------------------------------------- 1 | tests Package 2 | ============= 3 | 4 | :mod:`test_dmf_device` Module 5 | ----------------------------- 6 | 7 | .. automodule:: microdrop.tests.test_dmf_device 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`test_experiment_log` Module 13 | --------------------------------- 14 | 15 | .. automodule:: microdrop.tests.test_experiment_log 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`test_protocol` Module 21 | --------------------------- 22 | 23 | .. automodule:: microdrop.tests.test_protocol 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`update_dmf_control_board` Module 29 | -------------------------------------- 30 | 31 | .. automodule:: microdrop.tests.update_dmf_control_board 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | Project Modules 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | microdrop 8 | -------------------------------------------------------------------------------- /docs/rename.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pandas as pd 4 | from path_helpers import path 5 | 6 | 7 | def main(root, old_name, new_name): 8 | names = pd.Series([old_name, new_name], index=['old', 'new']) 9 | underscore_names = names.map(lambda v: v.replace('-', '_')) 10 | camel_names = names.str.split('-').map(lambda x: ''.join([y.title() 11 | for y in x])) 12 | 13 | # Replace all occurrences of provided original name with new name, and all 14 | # occurrences where dashes (i.e., '-') are replaced with underscores. 15 | # 16 | # Dashes are used in Python package names, but underscores are used in 17 | # Python module names. 18 | for p in path(root).walkfiles(): 19 | data = p.bytes() 20 | if '.git' not in p and (names.old in data or 21 | underscore_names.old in data or 22 | camel_names.old in data): 23 | p.write_bytes(data.replace(names.old, names.new) 24 | .replace(underscore_names.old, underscore_names.new) 25 | .replace(camel_names.old, camel_names.new)) 26 | 27 | def rename_path(p): 28 | if '.git' in p: 29 | return 30 | if underscore_names.old in p.name: 31 | p.rename(p.parent.joinpath(p.name.replace(underscore_names.old, 32 | underscore_names.new))) 33 | if camel_names.old in p.name: 34 | p.rename(p.parent.joinpath(p.name.replace(camel_names.old, 35 | camel_names.new))) 36 | 37 | # Rename all files/directories containing original name with new name, and 38 | # all occurrences where dashes (i.e., '-') are replaced with underscores. 39 | # 40 | # Process list of paths in *reverse order* to avoid renaming parent 41 | # directories before children. 42 | for p in sorted(list(path(root).walkdirs()))[-1::-1]: 43 | rename_path(p) 44 | 45 | for p in path(root).walkfiles(): 46 | rename_path(p) 47 | 48 | 49 | def parse_args(args=None): 50 | """Parses arguments, returns (options, args).""" 51 | from argparse import ArgumentParser 52 | 53 | if args is None: 54 | args = sys.argv 55 | 56 | parser = ArgumentParser(description='Rename template project with' 57 | 'hyphen-separated (path names and in ' 58 | 'files).') 59 | parser.add_argument('new_name', help='New project name (e.g., ' 60 | ' `my-new-project`)') 61 | 62 | args = parser.parse_args() 63 | return args 64 | 65 | 66 | if __name__ == '__main__': 67 | args = parse_args() 68 | main('.', 'microdrop', args.new_name) 69 | -------------------------------------------------------------------------------- /microdrop/__init__.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from path_helpers import path 4 | 5 | from ._version import get_versions 6 | 7 | #: ..versionadded:: 2.17 8 | __version__ = get_versions()['version'] 9 | del get_versions 10 | 11 | 12 | #: .. versionadded:: 2.13 13 | MICRODROP_PARSER = ArgumentParser(description='MicroDrop: graphical user ' 14 | 'interface for the DropBot Digital ' 15 | 'Microfluidics control system.', 16 | add_help=False) 17 | MICRODROP_PARSER.add_argument('-c', '--config', type=path, default=None) 18 | 19 | 20 | def base_path(): 21 | return path(__file__).abspath().parent 22 | 23 | 24 | def glade_path(): 25 | ''' 26 | Return path to `.glade` files used by `gtk` to construct views. 27 | ''' 28 | return base_path().joinpath('gui', 'glade') 29 | -------------------------------------------------------------------------------- /microdrop/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import runpy 3 | from path_helpers import path 4 | 5 | 6 | def base_path(): 7 | return path(__file__).abspath().parent 8 | 9 | sys.path.insert(0, str(base_path().parent)) 10 | 11 | runpy.run_module("microdrop.microdrop", run_name="__main__") 12 | -------------------------------------------------------------------------------- /microdrop/app_context.py: -------------------------------------------------------------------------------- 1 | ''' 2 | .. versionchanged:: 2.26 3 | Add screen geometry and window titlebar height constants. 4 | ''' 5 | import os 6 | 7 | import gtk 8 | 9 | 10 | def get_app(): 11 | import plugin_manager 12 | 13 | class_ = plugin_manager.get_service_class('App', env='microdrop') 14 | return plugin_manager.get_service_instance(class_, env='microdrop') 15 | 16 | 17 | def get_hub_uri(): 18 | from plugin_manager import get_service_instance_by_name 19 | 20 | hub_plugin = get_service_instance_by_name('microdrop.zmq_hub_plugin', 21 | env='microdrop') 22 | hub_uri = hub_plugin.get_app_values().get('hub_uri') 23 | if hub_uri is not None: 24 | return hub_uri.replace('*', 'localhost') 25 | 26 | 27 | # Application version used when querying update server for plugins, etc. 28 | APP_VERSION = {'major': 2, 'minor': 0, 'micro': 0} 29 | 30 | # Operating modes 31 | # =============== 32 | #: Programming mode 33 | MODE_PROGRAMMING = 1 << 0 34 | #: Programming mode with real-time enabled 35 | MODE_REAL_TIME_PROGRAMMING = 1 << 1 36 | #: Protocol running 37 | MODE_RUNNING = 1 << 2 38 | #: Protocol running with real-time enabled 39 | MODE_REAL_TIME_RUNNING = 1 << 3 40 | 41 | MODE_REAL_TIME_MASK = MODE_REAL_TIME_PROGRAMMING | MODE_REAL_TIME_RUNNING 42 | MODE_RUNNING_MASK = MODE_RUNNING | MODE_REAL_TIME_RUNNING 43 | MODE_PROGRAMMING_MASK = MODE_PROGRAMMING | MODE_REAL_TIME_PROGRAMMING 44 | 45 | SCREEN_HEIGHT = int(os.environ.get('SCREEN_HEIGHT', gtk.gdk.screen_height())) 46 | SCREEN_WIDTH = int(os.environ.get('SCREEN_WIDTH', gtk.gdk.screen_width())) 47 | SCREEN_LEFT = int(os.environ.get('SCREEN_LEFT', 0)) 48 | SCREEN_TOP = int(os.environ.get('SCREEN_TOP', 0)) 49 | TITLEBAR_HEIGHT = int(os.environ.get('TITLEBAR_HEIGHT', 23)) 50 | -------------------------------------------------------------------------------- /microdrop/bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/bin/__init__.py -------------------------------------------------------------------------------- /microdrop/bin/config.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Show or edit MicroDrop configuration. 3 | 4 | .. versionadded:: 2.13 5 | ''' 6 | import argparse 7 | import io 8 | import json 9 | import re 10 | import sys 11 | 12 | import configobj 13 | import microdrop as md 14 | import microdrop.config 15 | import pydash 16 | import yaml 17 | 18 | 19 | def _config_parser(): 20 | parser = argparse.ArgumentParser(parents=[md.MICRODROP_PARSER], 21 | add_help=False) 22 | 23 | subparsers = parser.add_subparsers(dest='command', help='commands') 24 | 25 | subparsers.add_parser('locate', help='Show path to configuration ' 26 | 'source') 27 | 28 | show = subparsers.add_parser('show', help='Show configuration') 29 | show.add_argument('--get', metavar='KEY') 30 | show_format = show.add_mutually_exclusive_group() 31 | show_format.add_argument('--json', action='store_true') 32 | show_format.add_argument('--yaml', action='store_true') 33 | 34 | edit = subparsers.add_parser('edit', help='Modify configuration') 35 | edit.add_argument('-n', '--dry-run', action='store_true') 36 | edit_args = edit.add_mutually_exclusive_group(required=True) 37 | edit_args.add_argument('--append', nargs=2, metavar=('KEY', 'VALUE'), 38 | help='Add one configuration value to the beginning ' 39 | 'of a list key.') 40 | edit_args.add_argument('--prepend', nargs=2, metavar=('KEY', 'VALUE'), 41 | help='Add one configuration value to the end of a ' 42 | 'list key.') 43 | edit_args.add_argument('--set', nargs=2, metavar=('KEY', 'VALUE'), 44 | help='Set a boolean or string key') 45 | edit_args.add_argument('--remove', nargs=2, metavar=('KEY', 'VALUE'), 46 | help='Remove all instances of configuration value ' 47 | 'from a list key.') 48 | edit_args.add_argument('--remove-key', metavar='KEY', help='Remove a ' 49 | 'configuration key (and all its values).') 50 | return parser 51 | 52 | 53 | CONFIG_PARSER = _config_parser() 54 | 55 | 56 | def parse_args(args=None): 57 | """Parses arguments, returns (options, args).""" 58 | if args is None: 59 | args = sys.argv[1:] 60 | 61 | parser = argparse.ArgumentParser(parents=[CONFIG_PARSER]) 62 | args = parser.parse_args(args) 63 | return args 64 | 65 | 66 | def main(args=None): 67 | ''' 68 | Wrap :func:`config` with integer return code. 69 | 70 | Parameters 71 | ---------- 72 | args : argparse.Namespace, optional 73 | Arguments as parsed by :func:`parse_args`. 74 | 75 | See also 76 | -------- 77 | :func:`parse_args` 78 | ''' 79 | config(args) 80 | # Return 81 | return 0 82 | 83 | 84 | def config(args=None): 85 | ''' 86 | Parameters 87 | ---------- 88 | args : argparse.Namespace, optional 89 | Arguments as parsed by :func:`parse_args`. 90 | 91 | See also 92 | -------- 93 | :func:`parse_args` 94 | 95 | Returns 96 | ------- 97 | configobj.ConfigObj 98 | Parsed (and potentially modified) configuration. 99 | ''' 100 | if args is None: 101 | args = parse_args() 102 | 103 | config = md.config.Config(args.config) 104 | 105 | if args.command == 'locate': 106 | print config.filename 107 | elif args.command == 'show': 108 | if args.get: 109 | data = pydash.get(config.data.dict(), args.get) 110 | else: 111 | data = config.data.dict() 112 | 113 | if args.json: 114 | # Output in JSON. 115 | json.dump(obj=data, fp=sys.stdout, indent=4) 116 | elif args.yaml: 117 | # Output in YAML format. 118 | print yaml.dump(data, default_flow_style=False), 119 | elif isinstance(data, dict): 120 | # Output in `ini` format. 121 | output = io.BytesIO() 122 | configobj.ConfigObj(data).write(output) 123 | print output.getvalue(), 124 | else: 125 | print data 126 | elif args.command == 'edit': 127 | for action_i in ('append', 'prepend', 'set', 'remove', 'remove_key'): 128 | if getattr(args, action_i): 129 | action = action_i 130 | break 131 | 132 | if action in ('append', 'prepend', 'set', 'remove'): 133 | # Unpack key and new value. 134 | key, new_value = getattr(args, action) 135 | 136 | # Look up existing value. 137 | config_value = pydash.get(config.data, key) 138 | 139 | if action == 'set': 140 | # Set a key to a string value. 141 | 142 | # Create dictionary structure containing only the specified key 143 | # and value. 144 | nested_value = pydash.set_({}, key, new_value) 145 | # Merge nested value into existing configuration structure. 146 | pydash.merge(config.data, nested_value) 147 | else: 148 | # Action is a list action. 149 | 150 | if config_value is None: 151 | # Create dictionary structure containing only empty list for 152 | # specified key. 153 | config_value = [] 154 | nested_value = pydash.set_({}, key, config_value) 155 | # Merge nested value into existing configuration structure. 156 | pydash.merge(config.data, nested_value) 157 | elif not isinstance(config_value, list): 158 | print >> sys.stderr, 'Value at %s is not a list.' % key 159 | raise SystemExit(1) 160 | 161 | if new_value in config_value: 162 | # Remove value even if we are appending or prepending to 163 | # avoid duplicate values. 164 | config_value.remove(new_value) 165 | 166 | if args.append: 167 | config_value.append(new_value) 168 | elif args.prepend: 169 | config_value.insert(0, new_value) 170 | elif action == 'remove_key': 171 | key = getattr(args, action) 172 | 173 | if pydash.get(config.data, key) is not None: 174 | # Key exists. 175 | 176 | # Split key into levels. 177 | # Use [negative lookbehind assertion][1] to only split on 178 | # non-escaped '.' characters. 179 | # 180 | # [1]: https://stackoverflow.com/a/21107911/345236 181 | levels = re.split(r'(?> sys.stderr, 'Error importing command_plugin' 7 | -------------------------------------------------------------------------------- /microdrop/core_plugins/command_plugin/microdrop_plugin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from logging_helpers import _L 4 | from pygtkhelpers.gthreads import gtk_threadsafe 5 | import threading 6 | import zmq 7 | 8 | from .plugin import CommandZmqPlugin 9 | from ...app_context import get_hub_uri 10 | from ...plugin_helpers import hub_execute 11 | from ...plugin_manager import (PluginGlobals, SingletonPlugin, IPlugin, 12 | implements) 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | PluginGlobals.push_env('microdrop') 18 | 19 | 20 | class CommandPlugin(SingletonPlugin): 21 | """ 22 | This class is automatically registered with the PluginManager. 23 | """ 24 | implements(IPlugin) 25 | plugin_name = 'microdrop.command_plugin' 26 | 27 | def __init__(self): 28 | self.name = self.plugin_name 29 | self.plugin = None 30 | self.stopped = threading.Event() 31 | 32 | def on_plugin_enable(self): 33 | """ 34 | Handler called once the plugin instance is enabled. 35 | 36 | Note: if you inherit your plugin from AppDataController and don't 37 | implement this handler, by default, it will automatically load all 38 | app options from the config file. If you decide to overide the 39 | default handler, you should call: 40 | 41 | AppDataController.on_plugin_enable(self) 42 | 43 | to retain this functionality. 44 | 45 | .. versionchanged:: 2.11.2 46 | Launch background thread to monitor plugin ZeroMQ command socket. 47 | 48 | Use :func:`gtk_threadsafe` decorator to wrap thread-related code 49 | to ensure GTK/GDK are initialized properly for a threaded 50 | application. 51 | """ 52 | self.cleanup() 53 | 54 | zmq_ready = threading.Event() 55 | 56 | def _check_command_socket(wait_duration_s): 57 | ''' 58 | Process each incoming message on the ZeroMQ plugin command socket. 59 | 60 | Stop listening if :attr:`stopped` event is set. 61 | ''' 62 | self.stopped.clear() 63 | self.plugin = CommandZmqPlugin(self, self.name, get_hub_uri()) 64 | # Initialize sockets. 65 | self.plugin.reset() 66 | zmq_ready.set() 67 | while not self.stopped.wait(wait_duration_s): 68 | try: 69 | msg_frames = (self.plugin.command_socket 70 | .recv_multipart(zmq.NOBLOCK)) 71 | except zmq.Again: 72 | pass 73 | else: 74 | self.plugin.on_command_recv(msg_frames) 75 | 76 | thread = threading.Thread(target=_check_command_socket, 77 | args=(0.01, )) 78 | thread.daemon = True 79 | thread.start() 80 | zmq_ready.wait() 81 | 82 | def cleanup(self): 83 | self.stopped.set() 84 | if self.plugin is not None: 85 | self.plugin = None 86 | 87 | def on_plugin_enabled(self, *args, **kwargs): 88 | # A plugin was enabled. Call `get_commands()` on self to trigger 89 | # refresh of commands in case the enabled plugin registered a command. 90 | hub_execute(self.name, 'get_commands') 91 | _L().info('refreshed registered commands.') 92 | 93 | def on_plugin_disable(self): 94 | """ 95 | Handler called once the plugin instance is disabled. 96 | """ 97 | self.cleanup() 98 | 99 | def on_app_exit(self): 100 | """ 101 | Handler called just before the MicroDrop application exits. 102 | """ 103 | self.cleanup() 104 | 105 | 106 | PluginGlobals.pop_env() 107 | -------------------------------------------------------------------------------- /microdrop/core_plugins/command_plugin/plugin.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process 2 | import logging 3 | import sys 4 | 5 | from zmq_plugin.plugin import Plugin as ZmqPlugin 6 | from zmq_plugin.schema import decode_content_data 7 | import pandas as pd 8 | 9 | from logging_helpers import _L #: .. versionadded:: 2.20 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class CommandZmqPlugin(ZmqPlugin): 16 | ''' 17 | API for registering commands. 18 | ''' 19 | def __init__(self, parent, *args, **kwargs): 20 | self.parent = parent 21 | self.control_board = None 22 | self._commands = pd.DataFrame(None, columns=['namespace', 23 | 'plugin_name', 24 | 'command_name', 'title']) 25 | super(CommandZmqPlugin, self).__init__(*args, **kwargs) 26 | 27 | def on_execute__unregister_command(self, request): 28 | data = decode_content_data(request) 29 | commands = self._commands 30 | ix = commands.loc[(commands.namespace == data['namespace']) & 31 | (commands.plugin_name == data['plugin_name']) & 32 | (commands.command_name == data['command_name']) & 33 | (commands.title == data['title'])].index 34 | self._commands.drop(ix, inplace=True) 35 | self._commands.reset_index(drop=True, inplace=True) 36 | return self.commands 37 | 38 | def on_execute__register_command(self, request): 39 | data = decode_content_data(request) 40 | plugin_name = data.get('plugin_name', request['header']['source']) 41 | return self.register_command(plugin_name, data['command_name'], 42 | namespace=data.get('namespace', ''), 43 | title=data.get('title')) 44 | 45 | def on_execute__get_commands(self, request): 46 | return self.commands 47 | 48 | def register_command(self, plugin_name, command_name, namespace='', 49 | title=None): 50 | ''' 51 | Register command. 52 | 53 | Each command is unique by: 54 | 55 | (namespace, plugin_name, command_name) 56 | ''' 57 | if title is None: 58 | title = (command_name[:1].upper() + 59 | command_name[1:]).replace('_', ' ') 60 | row_i = dict(zip(self._commands, [namespace, plugin_name, command_name, 61 | title])) 62 | self._commands = self._commands.append(row_i, ignore_index=True) 63 | return self.commands 64 | 65 | @property 66 | def commands(self): 67 | ''' 68 | Returns 69 | ------- 70 | pd.Series 71 | Series of command groups, where each group name maps to a series of 72 | commands. 73 | ''' 74 | return self._commands.copy() 75 | 76 | 77 | def parse_args(args=None): 78 | """Parses arguments, returns (options, args).""" 79 | from argparse import ArgumentParser 80 | 81 | if args is None: 82 | args = sys.argv 83 | 84 | parser = ArgumentParser(description='ZeroMQ Plugin process.') 85 | log_levels = ('critical', 'error', 'warning', 'info', 'debug', 'notset') 86 | parser.add_argument('-l', '--log-level', type=str, choices=log_levels, 87 | default='info') 88 | parser.add_argument('hub_uri') 89 | parser.add_argument('name', type=str) 90 | 91 | args = parser.parse_args() 92 | args.log_level = getattr(logging, args.log_level.upper()) 93 | return args 94 | 95 | 96 | if __name__ == '__main__': 97 | from zmq_plugin.bin.plugin import run_plugin 98 | 99 | def run_plugin_process(uri, name, subscribe_options, log_level): 100 | plugin_process = Process(target=run_plugin, 101 | args=()) 102 | plugin_process.daemon = False 103 | plugin_process.start() 104 | 105 | args = parse_args() 106 | 107 | logging.basicConfig(level=args.log_level) 108 | task = CommandZmqPlugin(None, args.name, args.hub_uri, {}) 109 | run_plugin(task, args.log_level) 110 | -------------------------------------------------------------------------------- /microdrop/core_plugins/device_info_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | import cPickle as pickle 2 | import threading 3 | 4 | from pygtkhelpers.gthreads import gtk_threadsafe 5 | from pygtkhelpers.schema import schema_dialog 6 | from zmq_plugin.plugin import Plugin as ZmqPlugin 7 | from zmq_plugin.schema import decode_content_data 8 | import zmq 9 | 10 | from ...app_context import get_app, get_hub_uri 11 | from ...plugin_helpers import hub_execute, hub_execute_async 12 | from ...plugin_manager import (IPlugin, PluginGlobals, ScheduleRequest, 13 | SingletonPlugin, emit_signal, implements) 14 | 15 | 16 | class DeviceInfoZmqPlugin(ZmqPlugin): 17 | def on_execute__get_device(self, request): 18 | app = get_app() 19 | return app.dmf_device 20 | 21 | def on_execute__get_svg_frame(self, request): 22 | app = get_app() 23 | return app.dmf_device.get_svg_frame() 24 | 25 | def on_execute__get_electrode_channels(self, request): 26 | app = get_app() 27 | return app.dmf_device.get_electrode_channels() 28 | 29 | def on_execute__set_electrode_channels(self, request): 30 | ''' 31 | Set channels for electrode `electrode_id` to `channels`. 32 | 33 | This includes updating `self.df_electrode_channels`. 34 | 35 | .. note:: Existing channels assigned to electrode are overwritten. 36 | 37 | Parameters 38 | ---------- 39 | electrode_id : str 40 | Electrode identifier. 41 | channels : list 42 | List of channel identifiers assigned to the electrode. 43 | 44 | .. versionchanged:: 2.25 45 | Emit ``on_dmf_device_changed`` in main GTK thread to ensure 46 | thread-safety. 47 | ''' 48 | data = decode_content_data(request) 49 | app = get_app() 50 | modified = (app.dmf_device 51 | .set_electrode_channels(data['electrode_id'], 52 | data['channels'])) 53 | if modified: 54 | gtk_threadsafe(emit_signal)("on_dmf_device_changed", 55 | [app.dmf_device]) 56 | return modified 57 | 58 | def on_execute__dumps(self, request): 59 | app = get_app() 60 | return pickle.dumps(app.dmf_device) 61 | 62 | def on_execute__edit_electrode_channels(self, request): 63 | ''' 64 | Display dialog to edit the channels mapped to the specified electrode. 65 | 66 | Parameters 67 | ---------- 68 | request : dict 69 | Request with decoded data field: 70 | - ``electrode_id``: electrode identifier (``str``). 71 | - e.g., ``"electrode028"`` 72 | 73 | .. versionadded:: 2.25 74 | ''' 75 | data = decode_content_data(request) 76 | electrode_id = data['electrode_id'] 77 | app = get_app() 78 | 79 | # Create schema to only accept a well-formed comma-separated list 80 | # of integer channel numbers. Default to list of channels 81 | # currently mapped to electrode. 82 | if electrode_id in app.dmf_device.channels_by_electrode.index: 83 | # If there is a single channel mapped to the electrode, the 84 | # `...ix[electrode_id]` lookup below returns a `pandas.Series`. 85 | # However, if multiple channels are mapped to the electrode the 86 | # `...ix[electrode_id]` lookup returns a `pandas.DataFrame`. 87 | # Calling `.values.ravel()` returns data in the same form in either 88 | # situation. 89 | current_channels = (app.dmf_device.channels_by_electrode 90 | .ix[[electrode_id]].values.ravel().tolist()) 91 | else: 92 | # Electrode has no channels currently mapped to it. 93 | current_channels = [] 94 | schema = {'type': 'object', 95 | 'properties': {'channels': 96 | {'type': 'string', 'pattern': 97 | r'^(\d+\s*(,\s*\d+\s*)*)?$', 98 | 'default': ','.join(map(str, 99 | current_channels))}}} 100 | 101 | @gtk_threadsafe 102 | def _dialog(): 103 | try: 104 | # Prompt user to enter a list of channel numbers (or nothing). 105 | result = schema_dialog(schema, device_name=False, 106 | parent=app.main_window_controller.view) 107 | except ValueError: 108 | pass 109 | else: 110 | # Well-formed (according to schema pattern) comma-separated 111 | # list of channels was provided. 112 | channels = sorted(set(map(int, filter(len, result['channels'] 113 | .split(','))))) 114 | hub_execute(self.name, 'set_electrode_channels', 115 | electrode_id=electrode_id, channels=channels) 116 | _dialog() 117 | 118 | 119 | PluginGlobals.push_env('microdrop') 120 | 121 | 122 | class DeviceInfoPlugin(SingletonPlugin): 123 | """ 124 | This class is automatically registered with the PluginManager. 125 | """ 126 | implements(IPlugin) 127 | plugin_name = 'microdrop.device_info_plugin' 128 | 129 | def __init__(self): 130 | self.name = self.plugin_name 131 | self.plugin = None 132 | self.stopped = threading.Event() 133 | 134 | def on_plugin_enable(self): 135 | """ 136 | .. versionchanged:: 2.11.2 137 | Use :func:`gtk_threadsafe` decorator to wrap thread-related code 138 | to ensure GTK/GDK are initialized properly for a threaded 139 | application. 140 | .. versionchanged:: 2.15.2 141 | Once enabled, do not stop socket listening thread. Re-enabling the 142 | plugin will cause the listening thread to be restarted. 143 | 144 | This ensures that calls to 145 | :func:`microdrop.plugin_manager.hub_execute` continue to work as 146 | expected even after ``on_app_exit`` signal is emitted. 147 | .. versionchanged:: 2.25 148 | Register ``"Edit electrode channels..."`` command with command 149 | plugin. 150 | """ 151 | if self.plugin is not None: 152 | self.cleanup() 153 | 154 | self.plugin = DeviceInfoZmqPlugin(self.name, get_hub_uri()) 155 | 156 | zmq_ready = threading.Event() 157 | 158 | def _check_command_socket(wait_duration_s): 159 | ''' 160 | Process each incoming message on the ZeroMQ plugin command socket. 161 | 162 | Stop listening if :attr:`stopped` event is set. 163 | ''' 164 | # Initialize sockets. 165 | self.plugin.reset() 166 | zmq_ready.set() 167 | self.stopped.clear() 168 | while not self.stopped.wait(wait_duration_s): 169 | try: 170 | msg_frames = (self.plugin.command_socket 171 | .recv_multipart(zmq.NOBLOCK)) 172 | self.plugin.on_command_recv(msg_frames) 173 | except zmq.Again: 174 | # No message ready. 175 | pass 176 | except ValueError: 177 | # Message was empty or not valid JSON. 178 | pass 179 | 180 | thread = threading.Thread(target=_check_command_socket, args=(0.01, )) 181 | thread.daemon = True 182 | thread.start() 183 | zmq_ready.wait() 184 | 185 | hub_execute_async('microdrop.command_plugin', 'register_command', 186 | command_name='edit_electrode_channels', 187 | namespace='electrode', plugin_name=self.name, 188 | title='Edit electrode _channels...') 189 | 190 | def cleanup(self): 191 | self.stopped.set() 192 | if self.plugin is not None: 193 | self.plugin = None 194 | 195 | @gtk_threadsafe 196 | def on_dmf_device_changed(self, device): 197 | ''' 198 | Notify other plugins that device has been modified. 199 | 200 | .. versionadded:: 2.25 201 | ''' 202 | hub_execute(self.name, 'get_device') 203 | 204 | @gtk_threadsafe 205 | def on_dmf_device_swapped(self, old_device, new_device): 206 | ''' 207 | Notify other plugins that device has been swapped. 208 | ''' 209 | hub_execute(self.name, 'get_device') 210 | 211 | def get_schedule_requests(self, function_name): 212 | """ 213 | Returns a list of scheduling requests (i.e., ScheduleRequest instances) 214 | for the function specified by function_name. 215 | 216 | 217 | .. versionchanged:: 2.25 218 | Enable _after_ command plugin and zmq hub to ensure commands can be 219 | registered. 220 | """ 221 | if function_name == 'on_dmf_device_swapped': 222 | return [ScheduleRequest('microdrop.app', self.name)] 223 | elif function_name == 'on_plugin_enable': 224 | return [ScheduleRequest('microdrop.zmq_hub_plugin', self.name), 225 | ScheduleRequest('microdrop.command_plugin', self.name)] 226 | return [] 227 | 228 | 229 | PluginGlobals.pop_env() 230 | -------------------------------------------------------------------------------- /microdrop/core_plugins/electrode_controller_plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/core_plugins/electrode_controller_plugin/__init__.py -------------------------------------------------------------------------------- /microdrop/core_plugins/protocol_controller/execute.py: -------------------------------------------------------------------------------- 1 | ''' 2 | .. versionadded:: 2.35.0 3 | ''' 4 | import copy 5 | 6 | from logging_helpers import _L 7 | import blinker 8 | import trollius as asyncio 9 | 10 | from ...plugin_manager import emit_signal 11 | 12 | 13 | @asyncio.coroutine 14 | def execute_step(plugin_kwargs): 15 | ''' 16 | .. versionadded:: 2.32 17 | 18 | XXX Coroutine XXX 19 | 20 | Execute a single protocol step. 21 | 22 | Parameters 23 | ---------- 24 | plugin_kwargs : dict 25 | Plugin keyword arguments, indexed by plugin name. 26 | 27 | Returns 28 | ------- 29 | list 30 | Return values from plugin ``on_step_run()`` coroutines. 31 | ''' 32 | # Take snapshot of arguments for current step. 33 | plugin_kwargs = copy.deepcopy(plugin_kwargs) 34 | 35 | signals = blinker.Namespace() 36 | 37 | @asyncio.coroutine 38 | def notify_signals_connected(): 39 | yield asyncio.From(asyncio.sleep(0)) 40 | signals.signal('signals-connected').send(None) 41 | 42 | loop = asyncio.get_event_loop() 43 | # Get list of coroutine futures by emitting `on_step_run()`. 44 | plugin_step_tasks = emit_signal("on_step_run", args=[plugin_kwargs, 45 | signals]) 46 | future = asyncio.wait(plugin_step_tasks.values()) 47 | 48 | loop.create_task(notify_signals_connected()) 49 | result = yield asyncio.From(future) 50 | raise asyncio.Return(result) 51 | 52 | 53 | @asyncio.coroutine 54 | def execute_steps(steps, signals=None): 55 | ''' 56 | .. versionadded:: 2.32 57 | 58 | Parameters 59 | ---------- 60 | steps : list[dict] 61 | List of plugin keyword argument dictionaries. 62 | signals : blinker.Namespace, optional 63 | Signals namespace where signals are sent through. 64 | 65 | Signals 66 | ------- 67 | step-started 68 | Parameters:: 69 | - ``i``: step index 70 | - ``plugin_kwargs``: plugin keyword arguments 71 | - ``steps_count``: total number of steps 72 | step-completed 73 | Parameters:: 74 | - ``i``: step index 75 | - ``plugin_kwargs``: plugin keyword arguments 76 | - ``steps_count``: total number of steps 77 | - ``result``: list of plugin step return values 78 | ''' 79 | if signals is None: 80 | signals = blinker.Namespace() 81 | 82 | for i, step_i in enumerate(steps): 83 | # Send notification that step has completed. 84 | responses = signals.signal('step-started')\ 85 | .send('execute_steps', i=i, plugin_kwargs=step_i, 86 | steps_count=len(steps)) 87 | yield asyncio.From(asyncio.gather(*(r[1] for r in responses))) 88 | # XXX Execute `on_step_run` coroutines in background thread 89 | # event-loop. 90 | try: 91 | done, pending = yield asyncio.From(execute_step(step_i)) 92 | 93 | exceptions = [] 94 | 95 | for d in done: 96 | try: 97 | d.result() 98 | except Exception as exception: 99 | exceptions.append(exception) 100 | _L().debug('Error: %s', exception, exc_info=True) 101 | 102 | if exceptions: 103 | use_markup = False 104 | monospace_format = '%s' if use_markup else '%s' 105 | 106 | if len(exceptions) == 1: 107 | message = (' ' + monospace_format % exceptions[0]) 108 | elif exceptions: 109 | message = ('\n%s' % '\n'.join(' - ' + monospace_format 110 | % e for e in exceptions)) 111 | raise RuntimeError('Error executing step:%s' % message) 112 | except asyncio.CancelledError: 113 | _L().debug('Cancelling protocol.', exc_info=True) 114 | raise 115 | except Exception as exception: 116 | _L().debug('Error executing step: `%s`', exception, exc_info=True) 117 | raise 118 | else: 119 | # All plugins have completed the step. 120 | # Send notification that step has completed. 121 | responses = signals.signal('step-completed')\ 122 | .send('execute_steps', i=i, plugin_kwargs=step_i, 123 | result=[r.result() for r in done], 124 | steps_count=len(steps)) 125 | yield asyncio.From(asyncio.gather(*(r[1] for r in responses))) 126 | -------------------------------------------------------------------------------- /microdrop/core_plugins/zmq_hub_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from multiprocessing import Process 3 | import logging 4 | import signal 5 | import threading 6 | 7 | from asyncio_helpers import cancellable 8 | from flatland import Form, String, Enum 9 | from logging_helpers import _L 10 | from zmq_plugin.bin.hub import run_hub 11 | from zmq_plugin.hub import Hub 12 | from zmq_plugin.plugin import Plugin as ZmqPlugin 13 | import trollius as asyncio 14 | 15 | from ...app_context import get_hub_uri 16 | from ...plugin_helpers import AppDataController 17 | from ...plugin_manager import (PluginGlobals, SingletonPlugin, IPlugin, 18 | implements) 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | PluginGlobals.push_env('microdrop') 24 | 25 | 26 | def _safe_run_hub(*args, **kwargs): 27 | ''' 28 | .. versionadded:: 2.15.2 29 | 30 | Wrap :func:`run_hub` to catch ``SIGINT`` signal (i.e., when `control-C` is 31 | pressed). 32 | ''' 33 | signal.signal(signal.SIGINT, lambda *args: None) 34 | return run_hub(*args, **kwargs) 35 | 36 | 37 | class MicroDropHub(Hub): 38 | def on_command_recv(self, msg_frames): 39 | try: 40 | super(MicroDropHub, self).on_command_recv(msg_frames) 41 | except Exception: 42 | _L().error('Command socket message error.', exc_info=True) 43 | 44 | 45 | class ZmqHubPlugin(SingletonPlugin, AppDataController): 46 | """ 47 | This class is automatically registered with the PluginManager. 48 | """ 49 | implements(IPlugin) 50 | plugin_name = 'microdrop.zmq_hub_plugin' 51 | 52 | ''' 53 | AppFields 54 | --------- 55 | 56 | A flatland Form specifying application options for the current plugin. 57 | Note that nested Form objects are not supported. 58 | 59 | Since we subclassed AppDataController, an API is available to access and 60 | modify these attributes. This API also provides some nice features 61 | automatically: 62 | -all fields listed here will be included in the app options dialog 63 | (unless properties=dict(show_in_gui=False) is used) 64 | -the values of these fields will be stored persistently in the microdrop 65 | config file, in a section named after this plugin's name attribute 66 | ''' 67 | AppFields = Form.of( 68 | String.named('hub_uri').using(optional=True, default='tcp://*:31000'), 69 | Enum.named('log_level').using(default='info', optional=True) 70 | .valued('debug', 'info', 'warning', 'error', 'critical')) 71 | 72 | def __init__(self): 73 | self.name = self.plugin_name 74 | self.hub_process = None 75 | #: ..versionadded:: 2.25 76 | self.exec_thread = None 77 | 78 | def on_plugin_enable(self): 79 | ''' 80 | .. versionchanged:: 2.25 81 | Start asyncio event loop in background thread to process ZeroMQ hub 82 | execution requests. 83 | ''' 84 | super(ZmqHubPlugin, self).on_plugin_enable() 85 | app_values = self.get_app_values() 86 | 87 | self.cleanup() 88 | self.hub_process = Process(target=_safe_run_hub, 89 | args=(MicroDropHub(app_values['hub_uri'], 90 | self.name), 91 | getattr(logging, 92 | app_values['log_level'] 93 | .upper()))) 94 | # Set process as daemonic so it terminate when main process terminates. 95 | self.hub_process.daemon = True 96 | self.hub_process.start() 97 | _L().info('ZeroMQ hub process (pid=%s, daemon=%s)', 98 | self.hub_process.pid, self.hub_process.daemon) 99 | 100 | zmq_ready = threading.Event() 101 | 102 | @asyncio.coroutine 103 | def _exec_task(): 104 | self.zmq_plugin = ZmqPlugin('microdrop', get_hub_uri()) 105 | self.zmq_plugin.reset() 106 | zmq_ready.set() 107 | 108 | event = asyncio.Event() 109 | try: 110 | yield asyncio.From(event.wait()) 111 | except asyncio.CancelledError: 112 | _L().info('closing ZeroMQ execution event loop') 113 | 114 | self.zmq_exec_task = cancellable(_exec_task) 115 | self.exec_thread = threading.Thread(target=self.zmq_exec_task) 116 | self.exec_thread.deamon = True 117 | self.exec_thread.start() 118 | zmq_ready.wait() 119 | 120 | def cleanup(self): 121 | ''' 122 | .. versionchanged:: 2.25 123 | Stop asyncio event loop. 124 | ''' 125 | if self.hub_process is not None: 126 | self.hub_process.terminate() 127 | self.hub_process = None 128 | if self.exec_thread is not None: 129 | self.zmq_exec_task.cancel() 130 | self.exec_thread = None 131 | 132 | 133 | PluginGlobals.pop_env() 134 | -------------------------------------------------------------------------------- /microdrop/gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/gui/__init__.py -------------------------------------------------------------------------------- /microdrop/gui/app_options_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | 4 | from flatland.schema import Form 5 | from pygtkhelpers.forms import FormView 6 | from pygtkhelpers.gthreads import gtk_threadsafe 7 | from pygtkhelpers.proxy import proxy_for 8 | import gtk 9 | import pkgutil 10 | 11 | from ..app_context import get_app 12 | from logging_helpers import _L #: .. versionadded:: 2.20 13 | from ..plugin_manager import IPlugin, ExtensionPoint, emit_signal 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class AppOptionsController: 19 | def __init__(self): 20 | ''' 21 | .. versionchanged:: 2.21 22 | Read glade file using ``pkgutil`` to also support loading from 23 | ``.zip`` files (e.g., in app packaged with Py2Exe). 24 | ''' 25 | app = get_app() 26 | 27 | self.form_views = {} 28 | self.no_gui_names = set() 29 | 30 | builder = gtk.Builder() 31 | # Read glade file using `pkgutil` to also support loading from `.zip` 32 | # files (e.g., in app packaged with Py2Exe). 33 | glade_str = pkgutil.get_data(__name__, 34 | 'glade/app_options_dialog.glade') 35 | builder.add_from_string(glade_str) 36 | 37 | self.dialog = builder.get_object("app_options_dialog") 38 | self.frame_core_plugins = builder.get_object("frame_core_plugins") 39 | self.core_plugins_vbox = builder.get_object("core_plugins_vbox") 40 | self.plugin_form_vbox = builder.get_object("plugin_form_vbox") 41 | self.dialog.set_transient_for(app.main_window_controller.view) 42 | 43 | self.btn_ok = builder.get_object('btn_ok') 44 | self.btn_apply = builder.get_object('btn_apply') 45 | self.btn_cancel = builder.get_object('btn_cancel') 46 | 47 | builder.connect_signals(self) 48 | self.builder = builder 49 | 50 | def _get_app_values(self, plugin_name): 51 | observers = ExtensionPoint(IPlugin) 52 | service = observers.service(plugin_name) 53 | if not hasattr(service, 'get_app_values'): 54 | values = dict([(k, v.value) for k, v in self.forms[plugin_name] 55 | .from_defaults().iteritems()]) 56 | else: 57 | values = service.get_app_values() 58 | if not values: 59 | return {} 60 | else: 61 | return values 62 | 63 | def clear_form(self): 64 | ''' 65 | .. versionchanged:: 2.11.2 66 | Make :func:`_remove_plugin_form` private local function. 67 | ''' 68 | def _remove_plugin_form(x): 69 | if x != self.frame_core_plugins: 70 | self.plugin_form_vbox.remove(x) 71 | 72 | self.plugin_form_vbox.foreach(lambda x: _remove_plugin_form(x)) 73 | 74 | def run(self): 75 | # Empty plugin form vbox 76 | # Get list of app option forms 77 | self.forms = emit_signal('get_app_form_class') 78 | self.form_views = {} 79 | self.clear_form() 80 | app = get_app() 81 | self.no_gui_names = set() 82 | for name, form in self.forms.iteritems(): 83 | # For each form, generate a pygtkhelpers formview and append the 84 | # view onto the end of the plugin vbox 85 | 86 | if form is None: 87 | schema_entries = [] 88 | else: 89 | # Only include fields that do not have show_in_gui set to False 90 | # in 'properties' dictionary 91 | schema_entries = [f for f in form.field_schema 92 | if f.properties.get('show_in_gui', True)] 93 | if not schema_entries: 94 | self.no_gui_names.add(name) 95 | continue 96 | gui_form = Form.of(*schema_entries) 97 | FormView.schema_type = gui_form 98 | self.form_views[name] = FormView() 99 | if name in app.core_plugins: 100 | self.core_plugins_vbox.pack_start(self.form_views[name].widget) 101 | self.frame_core_plugins.show() 102 | else: 103 | expander = gtk.Expander() 104 | expander.set_label(name) 105 | expander.set_expanded(True) 106 | expander.add(self.form_views[name].widget) 107 | self.plugin_form_vbox.pack_start(expander) 108 | for form_name, form in self.forms.iteritems(): 109 | if form_name in self.no_gui_names: 110 | continue 111 | form_view = self.form_views[form_name] 112 | values = self._get_app_values(form_name) 113 | fields = set(values.keys()).intersection(form_view.form.fields) 114 | for field in fields: 115 | value = values[field] 116 | proxy = proxy_for(getattr(form_view, field)) 117 | proxy.set_widget_value(value) 118 | form_field = form_view.form.fields[field] 119 | form_field.label_widget.set_text( 120 | re.sub(r'_', ' ', field).title()) 121 | 122 | self.dialog.show_all() 123 | 124 | response = self.dialog.run() 125 | if response == gtk.RESPONSE_OK: 126 | self.apply() 127 | elif response == gtk.RESPONSE_CANCEL: 128 | pass 129 | self.dialog.hide() 130 | return response 131 | 132 | @gtk_threadsafe 133 | def on_btn_apply_clicked(self, widget, data=None): 134 | ''' 135 | .. versionchanged:: 2.3.3 136 | Wrap with :func:`gtk_threadsafe` decorator to ensure the code runs 137 | in the main GTK thread. 138 | ''' 139 | self.apply() 140 | 141 | def apply(self): 142 | for name, form_view in self.form_views.iteritems(): 143 | fields = form_view.form.fields.keys() 144 | attrs = {} 145 | for field in fields: 146 | if form_view.form.fields[field].element.validate(): 147 | attrs[field] = form_view.form.fields[field].element.value 148 | else: 149 | _L().error('Failed to set %s value for %s', field, name) 150 | if attrs: 151 | observers = ExtensionPoint(IPlugin) 152 | service = observers.service(name) 153 | service.set_app_values(attrs) 154 | -------------------------------------------------------------------------------- /microdrop/gui/cairo_view.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import gtk 4 | from pygtkhelpers.delegates import SlaveView 5 | 6 | 7 | class GtkCairoView(SlaveView): 8 | """ 9 | SlaveView for Cairo drawing surface. 10 | """ 11 | 12 | def __init__(self, width=None, height=None): 13 | if width is None: 14 | self.width = 640 15 | else: 16 | self.width = width 17 | if height is None: 18 | self.height = 480 19 | else: 20 | self.height = height 21 | super(GtkCairoView, self).__init__() 22 | 23 | def create_ui(self): 24 | self.widget = gtk.DrawingArea() 25 | self.widget.set_size_request(self.width, self.height) 26 | self.window_xid = None 27 | self._set_window_title = False 28 | 29 | def show_and_run(self): 30 | self._set_window_title = True 31 | import IPython 32 | gtk.timeout_add(1000, IPython.embed) 33 | super(GtkCairoView, self).show_and_run() 34 | 35 | def on_widget__realize(self, widget): 36 | if not self.widget.window.has_native(): 37 | # Note that this is required (at least for Windows) to ensure that 38 | # the DrawingArea has a native window assigned. In Windows, if 39 | # this is not done, the video is written to the parent OS window 40 | # (not a "window" in the traditional sense of an app, but rather in 41 | # the window manager clipped rectangle sense). The symptom is that 42 | # the video will be drawn over top of any widgets, etc. in the 43 | # parent window. 44 | if not self.widget.window.ensure_native(): 45 | raise RuntimeError, 'Failed to get native window handle' 46 | if os.name == 'nt': 47 | self.window_xid = self.widget.window.handle 48 | else: 49 | self.window_xid = self.widget.window.xid 50 | # Copy window xid to clipboard 51 | clipboard = gtk.Clipboard() 52 | clipboard.set_text(str(self.window_xid)) 53 | if self._set_window_title: 54 | self.widget.parent.set_title('[window_xid] %s' % self.window_xid) 55 | print '[window_xid] %s' % self.window_xid 56 | 57 | 58 | if __name__ == '__main__': 59 | view = GtkCairoView() 60 | view.widget.connect('destroy', gtk.main_quit) 61 | view.show_and_run() 62 | -------------------------------------------------------------------------------- /microdrop/gui/channel_sweep.py: -------------------------------------------------------------------------------- 1 | from flatland import Form, Float 2 | from flatland.validation import ValueAtLeast 3 | from pygtkhelpers.ui.form_view_dialog import create_form_view 4 | from pygtkhelpers.ui.views.select import ListSelect 5 | import gtk 6 | import pandas as pd 7 | import pygtkhelpers.ui.extra_widgets # Include widget for `Float` form fields 8 | 9 | 10 | def get_channel_sweep_parameters(voltage=100, frequency=10e3, channels=None, 11 | parent=None): 12 | ''' 13 | Show dialog to select parameters for a sweep across a selected set of 14 | channels. 15 | 16 | Parameters 17 | ---------- 18 | voltage : int 19 | Default actuation voltage. 20 | frequency : int 21 | Default actuation frequency. 22 | channels : pandas.Series 23 | Default channels selection, encoded as boolean array indexed by channel 24 | number, where `True` values indicate selected channel(s). 25 | parent : gtk.Window 26 | If not ``None``, parent window for dialog. For example, display dialog 27 | at position relative to the parent window. 28 | 29 | Returns 30 | ------- 31 | dict 32 | Values collected from widgets with the following keys: 33 | `'frequency'`, `voltage'`, and (optionally) `'channels'`. 34 | ''' 35 | # Create a form view containing widgets to set the waveform attributes 36 | # (i.e., voltage and frequency). 37 | form = Form.of(Float.named('voltage') 38 | .using(default=voltage, 39 | validators=[ValueAtLeast(minimum=0)]), 40 | Float.named('frequency') 41 | .using(default=frequency, 42 | validators=[ValueAtLeast(minimum=1)])) 43 | form_view = create_form_view(form) 44 | 45 | # If default channel selection was provided, create a treeview with one row 46 | # per channel, and a checkbox in each row to mark the selection status of 47 | # the corresponding channel. 48 | if channels is not None: 49 | df_channel_select = pd.DataFrame(channels.index, columns=['channel']) 50 | df_channel_select.insert(1, 'select', channels.values) 51 | view_channels = ListSelect(df_channel_select) 52 | 53 | # Create dialog window. 54 | dialog = gtk.Dialog(title='Channel sweep parameters', 55 | buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, 56 | gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) 57 | 58 | # Add waveform widgets to dialog window. 59 | frame_waveform = gtk.Frame('Waveform properties') 60 | frame_waveform.add(form_view.widget) 61 | dialog.vbox.pack_start(child=frame_waveform, expand=False, fill=False, 62 | padding=5) 63 | 64 | # Add channel selection widgets to dialog window. 65 | if channels is not None: 66 | frame_channels = gtk.Frame('Select channels to sweep') 67 | frame_channels.add(view_channels.widget) 68 | dialog.vbox.pack_start(child=frame_channels, expand=True, fill=True, 69 | padding=5) 70 | 71 | # Mark all widgets as visible. 72 | dialog.vbox.show_all() 73 | 74 | if parent is not None: 75 | dialog.window.set_transient_for(parent) 76 | 77 | response = dialog.run() 78 | dialog.destroy() 79 | 80 | if response != gtk.RESPONSE_OK: 81 | raise RuntimeError('Dialog cancelled.') 82 | 83 | # Collection waveform and channel selection values from dialog. 84 | form_values = {name: f.element.value 85 | for name, f in form_view.form.fields.items()} 86 | 87 | if channels is not None: 88 | form_values['channels'] = (df_channel_select 89 | .loc[df_channel_select['select'], 90 | 'channel'].values) 91 | 92 | return form_values 93 | -------------------------------------------------------------------------------- /microdrop/gui/config_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ..app_context import get_app 4 | from logging_helpers import _L #: .. versionadded:: 2.20 5 | from ..plugin_manager import (IPlugin, SingletonPlugin, implements, 6 | PluginGlobals, ExtensionPoint, ScheduleRequest) 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | PluginGlobals.push_env('microdrop') 11 | 12 | 13 | class ConfigController(SingletonPlugin): 14 | implements(IPlugin) 15 | 16 | def __init__(self): 17 | self.name = "microdrop.gui.config_controller" 18 | self.app = None 19 | 20 | def on_plugin_enable(self): 21 | self.app = get_app() 22 | self.app.config_controller = self 23 | 24 | # load all app options from the config file 25 | observers = ExtensionPoint(IPlugin) 26 | for section_name, values_dict in self.app.config.data.iteritems(): 27 | service = observers.service(section_name) 28 | if service: 29 | if hasattr(service, 'set_app_values'): 30 | service.set_app_values(values_dict) 31 | else: 32 | _L().error('Invalid section in config file: [%s].', 33 | section_name) 34 | self.app.config.data.pop(section_name) 35 | 36 | def on_app_exit(self): 37 | self.app.config.save() 38 | 39 | def on_dmf_device_changed(self, dmf_device): 40 | device_name = None 41 | if dmf_device: 42 | device_name = dmf_device.name 43 | if self.app.config['dmf_device']['name'] != device_name: 44 | self.app.config['dmf_device']['name'] = device_name 45 | self.app.config.save() 46 | 47 | def on_dmf_device_swapped(self, old_dmf_device, dmf_device): 48 | self.on_dmf_device_changed(dmf_device) 49 | 50 | def on_protocol_changed(self): 51 | if self.app.protocol.name != self.app.config['protocol']['name']: 52 | self.app.config['protocol']['name'] = self.app.protocol.name 53 | self.app.config.save() 54 | 55 | def on_protocol_swapped(self, old_protocol, protocol): 56 | self.on_protocol_changed() 57 | 58 | def on_app_options_changed(self, plugin_name): 59 | if self.app is None: 60 | return 61 | _L().debug('on_app_options_changed: %s' % plugin_name) 62 | observers = ExtensionPoint(IPlugin) 63 | service = observers.service(plugin_name) 64 | if service: 65 | if not hasattr(service, 'get_app_values'): 66 | return 67 | app_options = service.get_app_values() 68 | if app_options: 69 | if plugin_name not in self.app.config.data: 70 | self.app.config.data[plugin_name] = {} 71 | self.app.config.data[plugin_name].update(app_options) 72 | self.app.config.save() 73 | 74 | def get_schedule_requests(self, function_name): 75 | """ 76 | Returns a list of scheduling requests (i.e., ScheduleRequest 77 | instances) for the function specified by function_name. 78 | """ 79 | if function_name == 'on_plugin_enable': 80 | return [ScheduleRequest("microdrop.gui.main_window_controller", 81 | self.name)] 82 | elif function_name == 'on_protocol_swapped': 83 | # make sure that the app's protocol reference is valid 84 | return [ScheduleRequest("microdrop.app", self.name)] 85 | elif function_name == 'on_dmf_device_swapped': 86 | # make sure that the app's dmf device reference is valid 87 | return [ScheduleRequest("microdrop.app", self.name)] 88 | return [] 89 | 90 | 91 | PluginGlobals.pop_env() 92 | -------------------------------------------------------------------------------- /microdrop/gui/experiment_log_controller.py: -------------------------------------------------------------------------------- 1 | ''' 2 | .. versionchanged:: 2.32.1 3 | Remove experiment log window. 4 | ''' 5 | from functools import wraps 6 | import os 7 | 8 | from logging_helpers import _L 9 | from pygtkhelpers.gthreads import gtk_threadsafe 10 | from pygtkhelpers.ui.extra_dialogs import yesno 11 | import gtk 12 | 13 | from .. import __version__ 14 | from ..app_context import get_app 15 | from ..experiment_log import ExperimentLog 16 | from ..plugin_manager import (IPlugin, SingletonPlugin, implements, 17 | PluginGlobals, emit_signal, ScheduleRequest, 18 | get_service_names, get_service_instance_by_name) 19 | from .dmf_device_controller import DEVICE_FILENAME 20 | 21 | 22 | PluginGlobals.push_env('microdrop') 23 | 24 | 25 | def require_experiment_log(log_level='info'): 26 | '''Decorator factory to require experiment log. 27 | 28 | Parameters 29 | ---------- 30 | log_level : str 31 | Level to log message to if DropBot is not connect. 32 | 33 | Returns 34 | ------- 35 | function 36 | Decorator to require experiment log. 37 | 38 | 39 | .. versionadded:: 2.32.3 40 | ''' 41 | def _require_experiment_log(func): 42 | @wraps(func) 43 | def _wrapped(*args, **kwargs): 44 | app = get_app() 45 | if not getattr(app, 'experiment_log', None): 46 | logger = _L() 47 | log_func = getattr(logger, log_level) 48 | log_func('No active experiment log.') 49 | else: 50 | return func(*args, **kwargs) 51 | return _wrapped 52 | return _require_experiment_log 53 | 54 | 55 | class ExperimentLogController(SingletonPlugin): 56 | ''' 57 | .. versionchanged:: 2.21 58 | Read glade file using ``pkgutil`` to also support loading from ``.zip`` 59 | files (e.g., in app packaged with Py2Exe). 60 | ''' 61 | implements(IPlugin) 62 | 63 | def __init__(self): 64 | self.name = "microdrop.gui.experiment_log_controller" 65 | 66 | ########################################################################### 67 | # Callback methods 68 | def on_app_exit(self): 69 | '''Save experiment info to log directory. 70 | 71 | 72 | .. versionchanged:: 2.28.1 73 | Do not create a new experiment after saving experiment log. 74 | ''' 75 | self.save() 76 | 77 | @require_experiment_log() 78 | def on_new_experiment(self, widget=None, data=None): 79 | '''Save current experiment info and create new experiment. 80 | 81 | 82 | .. versionchanged:: 2.32.3 83 | Use :meth:`create()` method to create new log directory. 84 | ''' 85 | _L().debug('New experiment clicked') 86 | self.save() 87 | app = get_app() 88 | self.create(app.experiment_log.directory) 89 | 90 | def on_plugin_enable(self): 91 | '''Register ``File > New Experiment`` callback; disable on app start. 92 | 93 | 94 | .. versionadded:: 2.32.3 95 | ''' 96 | app = get_app() 97 | app.experiment_log_controller = self 98 | self.menu_new_experiment = app.builder.get_object('menu_new_experiment') 99 | app.signals["on_menu_new_experiment_activate"] = self.on_new_experiment 100 | self.menu_new_experiment.set_sensitive(False) 101 | 102 | def on_dmf_device_swapped(self, old_dmf_device, dmf_device): 103 | ''' 104 | .. versionchanged:: 2.32.2 105 | Removed method. 106 | 107 | .. versionchanged:: 2.32.3 108 | Restored method since it is necessary to initialize an experiment 109 | log directory. Save experiment info (if available) before creating 110 | new experiment log. 111 | ''' 112 | if dmf_device and dmf_device.name: 113 | app = get_app() 114 | device_path = os.path.join(app.get_device_directory(), 115 | dmf_device.name, "logs") 116 | self.save() 117 | self.create(device_path) 118 | 119 | @require_experiment_log() 120 | def on_protocol_finished(self): 121 | '''Save experiment info to log directory; optionally create new log. 122 | 123 | 124 | .. versionchanged:: 2.32.3 125 | Use :meth:`create()` method to create new log directory. 126 | ''' 127 | self.save() 128 | 129 | @gtk_threadsafe 130 | def ask_create_new(): 131 | self.menu_new_experiment.set_sensitive(True) 132 | 133 | result = yesno('Experiment complete. Would you like to start a new' 134 | ' experiment?') 135 | if result == gtk.RESPONSE_YES: 136 | app = get_app() 137 | self.create(app.experiment_log.directory) 138 | 139 | app = get_app() 140 | if not app.experiment_log.empty: 141 | ask_create_new() 142 | 143 | @require_experiment_log() 144 | def save(self): 145 | '''Save experiment info to current log working directory. 146 | 147 | The ``app.experiment_log`` allows plugins to append arbitrary data 148 | objects to a list using the ``add_data()`` method. Until *this* method 149 | is called, the experiment log data is only stored in-memory. 150 | 151 | This method performs the following actions:: 152 | 153 | 1. Append experiment metadata to the experiment log data list, 154 | including MicroDrop version, device name, protocol name, and plugin 155 | versions. 156 | 2. Save in-memory experiment log data to disk in the current log 157 | working directory. 158 | 3. Write a copy of the active protocol to disk in the current log 159 | working directory. 160 | 4. Write a copy of the active device SVG to disk in the current log 161 | working directory. 162 | 163 | Note that steps **2**-**4** will overwrite the respective files if they 164 | already in the current log working directory. 165 | 166 | 167 | .. versionchanged:: 2.28.1 168 | Add :data:`create_new` parameter. 169 | 170 | .. versionchanged:: 2.32.1 171 | Save log directory if directory is not empty. 172 | ''' 173 | app = get_app() 174 | if app.experiment_log.empty: 175 | # Experiment log is empty, so do nothing. 176 | return 177 | 178 | data = {'software version': __version__} 179 | data['device name'] = app.dmf_device.name 180 | data['protocol name'] = app.protocol.name 181 | plugin_versions = {} 182 | for name in get_service_names(env='microdrop.managed'): 183 | service = get_service_instance_by_name(name) 184 | if service._enable: 185 | plugin_versions[name] = str(service.version) 186 | data['plugins'] = plugin_versions 187 | app.experiment_log.add_data(data) 188 | log_path = app.experiment_log.save() 189 | 190 | # Save the protocol to experiment log directory. 191 | app.protocol.save(os.path.join(log_path, 'protocol')) 192 | 193 | # Convert device to SVG string. 194 | svg_unicode = app.dmf_device.to_svg() 195 | # Save the device to experiment log directory. 196 | with open(os.path.join(log_path, DEVICE_FILENAME), 'wb') as output: 197 | output.write(svg_unicode) 198 | 199 | def create(self, directory): 200 | '''Create a new experiment log with corresponding log directory. 201 | 202 | .. versionadded:: 2.32.3 203 | ''' 204 | experiment_log = ExperimentLog(directory) 205 | emit_signal('on_experiment_log_changed', experiment_log) 206 | 207 | ########################################################################### 208 | # Accessor methods 209 | def get_schedule_requests(self, function_name): 210 | '''Request scheduling of certain signals relative to other plugins. 211 | 212 | 213 | .. versionchanged:: 2.32.3 214 | Request scheduling of ``on_plugin_enable`` handling after main 215 | window controller to ensure GTK builder reference is ready. 216 | ''' 217 | if function_name == 'on_experiment_log_changed': 218 | # Ensure that the app's reference to the new experiment log gets 219 | # set. 220 | return [ScheduleRequest('microdrop.app', self.name)] 221 | elif function_name == 'on_plugin_enable': 222 | # Ensure that the app's GTK builder is initialized. 223 | return [ScheduleRequest('microdrop.gui.main_window_controller', 224 | self.name)] 225 | return [] 226 | 227 | PluginGlobals.pop_env() 228 | -------------------------------------------------------------------------------- /microdrop/gui/field_filter_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | 4 | from flatland import Form, Boolean 5 | from pygtkhelpers.forms import FormView 6 | from pygtkhelpers.proxy import proxy_for 7 | import gtk 8 | import pkgutil 9 | 10 | from ..app_context import get_app 11 | from logging_helpers import _L #: .. versionadded:: 2.20 12 | from ..plugin_manager import IPlugin, ExtensionPoint 13 | 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class FieldFilterController(object): 19 | def __init__(self): 20 | ''' 21 | .. versionchanged:: 2.21 22 | Read glade file using ``pkgutil`` to also support loading from 23 | ``.zip`` files (e.g., in app packaged with Py2Exe). 24 | ''' 25 | app = get_app() 26 | builder = gtk.Builder() 27 | # Read glade file using `pkgutil` to also support loading from `.zip` 28 | # files (e.g., in app packaged with Py2Exe). 29 | glade_str = pkgutil.get_data(__name__, 30 | 'glade/app_options_dialog.glade') 31 | builder.add_from_string(glade_str) 32 | self.dialog = builder.get_object("app_options_dialog") 33 | self.frame_core_plugins = builder.get_object("frame_core_plugins") 34 | self.core_plugins_vbox = builder.get_object("core_plugins_vbox") 35 | self.plugin_form_vbox = builder.get_object("plugin_form_vbox") 36 | self.dialog.set_transient_for(app.main_window_controller.view) 37 | 38 | self.btn_ok = builder.get_object('btn_ok') 39 | self.btn_apply = builder.get_object('btn_apply') 40 | self.btn_cancel = builder.get_object('btn_cancel') 41 | self.form_views = {} 42 | self.enabled_fields_by_plugin = {} 43 | 44 | builder.connect_signals(self) 45 | self.builder = builder 46 | 47 | def _get_app_values(self, plugin_name): 48 | observers = ExtensionPoint(IPlugin) 49 | service = observers.service(plugin_name) 50 | if not hasattr(service, 'get_app_values'): 51 | values = dict([(k, v.value) for k, v in 52 | self.forms[plugin_name].from_defaults() 53 | .iteritems()]) 54 | else: 55 | values = service.get_app_values() 56 | if not values: 57 | return {} 58 | else: 59 | return values 60 | 61 | def remove_plugin_form(self, x): 62 | if x != self.frame_core_plugins: 63 | self.plugin_form_vbox.remove(x) 64 | 65 | def clear_form(self): 66 | self.plugin_form_vbox.foreach(lambda x: self.remove_plugin_form(x)) 67 | 68 | def run(self, forms, initial_values=None): 69 | # Empty plugin form vbox 70 | # Get list of app option forms 71 | self.forms = forms 72 | self.form_views = {} 73 | self.clear_form() 74 | app = get_app() 75 | core_plugins_count = 0 76 | for name, form in self.forms.iteritems(): 77 | # For each form, generate a pygtkhelpers formview and append the 78 | # view onto the end of the plugin vbox 79 | 80 | if len(form.field_schema) == 0: 81 | continue 82 | 83 | # Only include fields that do not have show_in_gui set to False in 84 | # 'properties' dictionary 85 | schema_entries = [f for f in form.field_schema 86 | if f.properties.get('show_in_gui', True)] 87 | gui_form = Form.of(*[Boolean.named(s.name).using(default=True, 88 | optional=True) 89 | for s in schema_entries]) 90 | FormView.schema_type = gui_form 91 | if not schema_entries: 92 | continue 93 | self.form_views[name] = FormView() 94 | if name in app.core_plugins: 95 | self.core_plugins_vbox.pack_start(self.form_views[name].widget) 96 | core_plugins_count += 1 97 | else: 98 | expander = gtk.Expander() 99 | expander.set_label(name) 100 | expander.set_expanded(True) 101 | expander.add(self.form_views[name].widget) 102 | self.plugin_form_vbox.pack_start(expander) 103 | if core_plugins_count == 0: 104 | self.frame_core_plugins.hide() 105 | self.plugin_form_vbox.remove(self.frame_core_plugins) 106 | else: 107 | if self.frame_core_plugins not in self.plugin_form_vbox.children(): 108 | self.plugin_form_vbox.pack_start(self.frame_core_plugins) 109 | self.frame_core_plugins.show() 110 | 111 | if not initial_values: 112 | initial_values = {} 113 | 114 | for form_name, form in self.forms.iteritems(): 115 | if not form.field_schema: 116 | continue 117 | form_view = self.form_views[form_name] 118 | values = initial_values.get(form_name, {}) 119 | for name, field in form_view.form.fields.items(): 120 | if name in values or not initial_values: 121 | value = True 122 | else: 123 | value = False 124 | _L().debug('set %s to %s', name, value) 125 | proxy = proxy_for(getattr(form_view, name)) 126 | proxy.set_widget_value(value) 127 | field.label_widget.set_text( 128 | re.sub(r'_', ' ', name).title()) 129 | 130 | self.dialog.show_all() 131 | 132 | response = self.dialog.run() 133 | if response == gtk.RESPONSE_OK: 134 | self.apply() 135 | elif response == gtk.RESPONSE_CANCEL: 136 | pass 137 | self.dialog.hide() 138 | return response 139 | 140 | def on_btn_apply_clicked(self, widget, data=None): 141 | self.apply() 142 | 143 | def apply(self): 144 | enabled_fields_by_plugin = {} 145 | for name, form_view in self.form_views.iteritems(): 146 | enabled_fields = set([f for f, v in form_view.form.fields.items() 147 | if v.element.value]) 148 | if enabled_fields: 149 | enabled_fields_by_plugin[name] = enabled_fields 150 | self.enabled_fields_by_plugin = enabled_fields_by_plugin 151 | -------------------------------------------------------------------------------- /microdrop/gui/glade/about_dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 5 8 | dialog 9 | μDrop 10 | Copyright 2020 Sci-Bots Inc. 11 | 12 | 13 | True 14 | False 15 | 2 16 | 17 | 18 | True 19 | False 20 | end 21 | 22 | 23 | False 24 | True 25 | end 26 | 0 27 | 28 | 29 | 30 | 31 | http://github.com/sci-bots/microdrop 32 | True 33 | True 34 | True 35 | True 36 | False 37 | none 38 | 0.50999999046325684 39 | http://github.com/sci-bots/microdrop 40 | 41 | 42 | True 43 | True 44 | 2 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /microdrop/gui/glade/app_options_dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 5 8 | Options 9 | 500 10 | dialog 11 | 12 | 13 | True 14 | False 15 | 2 16 | 17 | 18 | True 19 | False 20 | end 21 | 22 | 23 | True 24 | False 25 | 26 | 27 | gtk-apply 28 | True 29 | True 30 | True 31 | False 32 | True 33 | 34 | 35 | 36 | True 37 | True 38 | 0 39 | 40 | 41 | 42 | 43 | False 44 | False 45 | 0 46 | 47 | 48 | 49 | 50 | gtk-ok 51 | True 52 | True 53 | True 54 | False 55 | True 56 | 57 | 58 | False 59 | False 60 | 1 61 | 62 | 63 | 64 | 65 | gtk-cancel 66 | True 67 | True 68 | True 69 | False 70 | True 71 | 72 | 73 | False 74 | False 75 | 2 76 | 77 | 78 | 79 | 80 | False 81 | True 82 | end 83 | 0 84 | 85 | 86 | 87 | 88 | True 89 | False 90 | 91 | 92 | True 93 | False 94 | 5 95 | 0 96 | etched-out 97 | 98 | 99 | True 100 | False 101 | 12 102 | 103 | 104 | True 105 | False 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 0 116 | True 117 | False 118 | <b>Options</b> 119 | True 120 | 121 | 122 | 123 | 124 | True 125 | True 126 | 0 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | False 135 | False 136 | 1 137 | 138 | 139 | 140 | 141 | 142 | button_ok 143 | btn_cancel 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /microdrop/gui/glade/dmf_device_view.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 500 7 | 600 8 | False 9 | 10 | 11 | 500 12 | 600 13 | True 14 | True 15 | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /microdrop/gui/glade/dmf_device_view_context_menu.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | 9 | 10 | False 11 | True 12 | False 13 | Edit electrode channels 14 | 15 | 16 | 17 | 18 | False 19 | True 20 | False 21 | Edit electrode area 22 | 23 | 24 | 25 | 26 | False 27 | True 28 | False 29 | Clear drop routes 30 | 31 | 32 | 33 | 34 | False 35 | True 36 | False 37 | Clear all drop routes 38 | 39 | 40 | 41 | 42 | False 43 | True 44 | False 45 | IPython... 46 | 47 | 48 | 49 | 50 | False 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /microdrop/gui/glade/plugin_download_dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 480 7 | 480 8 | False 9 | 5 10 | Plugin manager 11 | True 12 | True 13 | dialog 14 | 15 | 16 | True 17 | False 18 | 2 19 | 20 | 21 | True 22 | False 23 | True 24 | end 25 | 26 | 27 | gtk-ok 28 | True 29 | True 30 | True 31 | False 32 | True 33 | 34 | 35 | False 36 | False 37 | 0 38 | 39 | 40 | 41 | 42 | gtk-cancel 43 | True 44 | True 45 | True 46 | False 47 | True 48 | 49 | 50 | False 51 | False 52 | 1 53 | 54 | 55 | 56 | 57 | False 58 | True 59 | end 60 | 0 61 | 62 | 63 | 64 | 65 | True 66 | False 67 | Please select plugin(s) to install 68 | 69 | 70 | False 71 | False 72 | 1 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | True 81 | False 82 | 83 | 84 | 85 | 86 | 87 | True 88 | True 89 | 3 90 | 91 | 92 | 93 | 94 | 95 | button_ok 96 | button_cancel 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /microdrop/gui/glade/plugin_manager_dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 480 7 | 480 8 | False 9 | 5 10 | Plugin manager 11 | True 12 | True 13 | dialog 14 | 15 | 16 | True 17 | False 18 | 2 19 | 20 | 21 | True 22 | False 23 | 24 | 25 | 26 | 27 | 28 | False 29 | False 30 | 0 31 | 32 | 33 | 34 | 35 | True 36 | False 37 | True 38 | end 39 | 40 | 41 | gtk-ok 42 | False 43 | True 44 | True 45 | True 46 | True 47 | 48 | 49 | False 50 | False 51 | 0 52 | 53 | 54 | 55 | 56 | False 57 | True 58 | end 59 | 0 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | button_ok 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /microdrop/gui/glade/text_input_dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 5 8 | Device name 9 | True 10 | dialog 11 | 12 | 13 | True 14 | False 15 | 2 16 | 17 | 18 | True 19 | False 20 | 21 | 22 | True 23 | False 24 | Label: 25 | True 26 | 27 | 28 | True 29 | True 30 | 5 31 | 0 32 | 33 | 34 | 35 | 36 | True 37 | True 38 | 39 | False 40 | False 41 | True 42 | True 43 | 44 | 45 | True 46 | True 47 | 1 48 | 49 | 50 | 51 | 52 | True 53 | True 54 | 0 55 | 56 | 57 | 58 | 59 | True 60 | False 61 | start 62 | 63 | 64 | gtk-ok 65 | True 66 | True 67 | True 68 | False 69 | True 70 | 71 | 72 | False 73 | False 74 | 0 75 | 76 | 77 | 78 | 79 | gtk-cancel 80 | True 81 | True 82 | True 83 | False 84 | True 85 | 86 | 87 | False 88 | False 89 | 1 90 | 91 | 92 | 93 | 94 | False 95 | True 96 | end 97 | 1 98 | 99 | 100 | 101 | 102 | 103 | button1 104 | button2 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /microdrop/gui/plugin_manager_controller.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import logging 3 | import sys 4 | import threading 5 | 6 | import gtk 7 | import path_helpers as ph 8 | import yaml 9 | 10 | from ..app_context import get_app 11 | from ..gui.plugin_manager_dialog import PluginManagerDialog 12 | from ..plugin_helpers import get_plugin_info 13 | from ..plugin_manager import (IPlugin, implements, SingletonPlugin, 14 | PluginGlobals, get_service_instance, 15 | enable as enable_service, 16 | disable as disable_service) 17 | 18 | logger = logging.getLogger(__name__) 19 | 20 | PluginGlobals.push_env('microdrop') 21 | 22 | 23 | class PluginController(object): 24 | ''' 25 | Manage an installed plugin. 26 | ''' 27 | def __init__(self, controller, name): 28 | self.controller = controller 29 | self.name = name 30 | self.plugin_env = PluginGlobals.env('microdrop.managed') 31 | # Look up running instance of plugin (i.e., service) based on name of 32 | # plugin class. 33 | services_by_class_name = {s.__class__.__name__: s 34 | for s in self.plugin_env.services} 35 | self.service = services_by_class_name[name] 36 | self.plugin_class = self.service.__class__ 37 | self.box = gtk.HBox() 38 | self.label = gtk.Label('%s' % self.service.name) 39 | self.label.set_alignment(0, 0.5) 40 | self.label_version = gtk.Label(str(self.version)) 41 | self.label_version.set_alignment(0, 0.5) 42 | self.button_enable = gtk.Button('Enable') 43 | self.button_enable.connect('clicked', self.on_button_enable_clicked, 44 | None) 45 | self.box.pack_start(self.label, expand=True, fill=True) 46 | self.box.pack_end(self.button_enable, expand=False, fill=False, 47 | padding=5) 48 | self.box.pack_end(self.label_version, expand=True, fill=False) 49 | self.update() 50 | 51 | self.box.show_all() 52 | 53 | @property 54 | def version(self): 55 | return getattr(self.plugin_class, 'version', None) 56 | 57 | def enabled(self): 58 | ''' 59 | Returns 60 | ------- 61 | bool 62 | ``True`` if plugin instance is enabled. 63 | 64 | Otherwise, ``False``. 65 | ''' 66 | return not(self.service is None or not self.service.enabled()) 67 | 68 | def update(self): 69 | ''' 70 | Update reference to plugin/service instance and update enable button 71 | state. 72 | ''' 73 | self.service = get_service_instance(self.plugin_class) 74 | if self.enabled(): 75 | self.button_enable.set_label('Disable') 76 | else: 77 | self.button_enable.set_label('Enable') 78 | 79 | def toggle_enabled(self): 80 | ''' 81 | Toggle enable state of plugin/service instance. 82 | ''' 83 | if not self.enabled(): 84 | enable_service(self.service.name) 85 | else: 86 | disable_service(self.service.name) 87 | self.update() 88 | 89 | def get_widget(self): 90 | ''' 91 | Returns 92 | ------- 93 | gtk.HBox 94 | UI widget instance. 95 | ''' 96 | return self.box 97 | 98 | @property 99 | def is_site_plugin(self): 100 | return (self.get_plugin_path().parent.realpath() == 101 | ph.path(sys.prefix).joinpath('etc', 'microdrop', 'plugins', 102 | 'enabled')) 103 | 104 | def get_plugin_info(self): 105 | ''' 106 | Returns 107 | ------- 108 | namedtuple 109 | Plugin metadata in the form 110 | ``(package_name, plugin_name, version)``. 111 | ''' 112 | return get_plugin_info(self.get_plugin_path()) 113 | 114 | def get_plugin_path(self): 115 | ''' 116 | Returns 117 | ------- 118 | path_helpers.path 119 | Path to plugin directory. 120 | ''' 121 | # Find path to file where plugin/service class is defined. 122 | class_def_file = ph.path(inspect.getfile(self.service.__class__)) 123 | 124 | return class_def_file.parent 125 | 126 | def on_button_enable_clicked(self, widget, data=None): 127 | ''' 128 | Handler for ``"Enable"/"Disable"`` button. 129 | ''' 130 | self.toggle_enabled() 131 | 132 | 133 | class PluginManagerController(SingletonPlugin): 134 | ''' 135 | Manage installed plugins. 136 | ''' 137 | implements(IPlugin) 138 | 139 | def __init__(self): 140 | self.name = 'microdrop.gui.plugin_manager_controller' 141 | self.plugins = [] 142 | # Maintain a list of path deletions to be processed on next app launch 143 | self.requested_deletions = [] 144 | self.rename_queue = [] 145 | self.restart_required = False 146 | self.plugin_env = PluginGlobals.env('microdrop.managed') 147 | self.dialog = PluginManagerDialog() 148 | # Event to indicate when update dialog is running to prevent another 149 | # dialog from being launched. 150 | self.update_dialog_running = threading.Event() 151 | 152 | def get_plugin_names(self): 153 | ''' 154 | Returns 155 | ------- 156 | list(str) 157 | List of plugin class names (e.g., ``['StepLabelPlugin', ...]``). 158 | ''' 159 | return list(self.plugin_env.plugin_registry.keys()) 160 | 161 | def update(self): 162 | ''' 163 | Update list of plugin controllers (one controller for each imported 164 | plugin in the ``microdrop.managed`` environment). 165 | 166 | ..notes:: 167 | Also update **deletion**, **rename**, and **post-install** queue 168 | files. 169 | ''' 170 | plugin_names = self.get_plugin_names() 171 | del self.plugins 172 | self.plugins = [] 173 | for name in plugin_names: 174 | plugin_controller = PluginController(self, name) 175 | # Skip the plugin if it has been marked for uninstall, or no 176 | # longer exists 177 | if (plugin_controller.get_plugin_path().abspath() in 178 | self.requested_deletions) or (not plugin_controller 179 | .get_plugin_path().isdir()): 180 | continue 181 | self.plugins.append(plugin_controller) 182 | 183 | # Save the list of path deletions to be processed on next app launch 184 | app = get_app() 185 | requested_deletion_path = (ph.path(app.config.data['plugins'] 186 | ['directory']) 187 | .joinpath('requested_deletions.yml')) 188 | requested_deletion_path.write_bytes(yaml.dump([p.abspath() 189 | for p in self 190 | .requested_deletions])) 191 | rename_queue_path = (ph.path(app.config.data['plugins']['directory']) 192 | .joinpath('rename_queue.yml')) 193 | rename_queue_path.write_bytes(yaml.dump([(p1.abspath(), p2.abspath()) 194 | for p1, p2 in 195 | self.rename_queue])) 196 | 197 | 198 | PluginGlobals.pop_env() 199 | -------------------------------------------------------------------------------- /microdrop/gui/plugin_manager_dialog.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | 4 | import gtk 5 | import gobject 6 | import pkgutil 7 | 8 | from ..app_context import get_app 9 | from logging_helpers import _L #: .. versionadded:: 2.20 10 | from ..plugin_manager import get_service_instance_by_name 11 | 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class PluginManagerDialog(object): 17 | ''' 18 | List installed plugins with the following action buttons for each plugin: 19 | 20 | - Enable 21 | - Disable 22 | - **TODO** Uninstall 23 | ''' 24 | def __init__(self): 25 | ''' 26 | .. versionchanged:: 2.21 27 | Read glade file using ``pkgutil`` to also support loading from 28 | ``.zip`` files (e.g., in app packaged with Py2Exe). 29 | ''' 30 | builder = gtk.Builder() 31 | # Read glade file using `pkgutil` to also support loading from `.zip` 32 | # files (e.g., in app packaged with Py2Exe). 33 | glade_str = pkgutil.get_data(__name__, 34 | 'glade/plugin_manager_dialog.glade') 35 | builder.add_from_string(glade_str) 36 | 37 | self.window = builder.get_object('plugin_manager') 38 | self.vbox_plugins = builder.get_object('vbox_plugins') 39 | builder.connect_signals(self) 40 | 41 | def clear_plugin_list(self): 42 | self.vbox_plugins.foreach(lambda x: self.vbox_plugins.remove(x)) 43 | 44 | @property 45 | def controller(self): 46 | plugin_name = 'microdrop.gui.plugin_manager_controller' 47 | service = get_service_instance_by_name(plugin_name, env='microdrop') 48 | return service 49 | 50 | def update(self): 51 | ''' 52 | Update plugin list widget. 53 | ''' 54 | self.clear_plugin_list() 55 | self.controller.update() 56 | for p in self.controller.plugins: 57 | self.vbox_plugins.pack_start(p.get_widget()) 58 | 59 | def run(self): 60 | ''' 61 | .. versionchanged:: 2.10.3 62 | Use :func:`plugin_helpers.get_plugin_info` function to retrieve 63 | package name. 64 | 65 | .. versionchanged:: 2.10.5 66 | Save Python module names of enabled plugins (**not** Conda package 67 | names) to ``microdrop.ini`` configuration file. 68 | ''' 69 | # TODO 70 | # ---- 71 | # 72 | # - [ ] Remove all references to `app` 73 | # - [ ] Use `MICRODROP_CONDA_ETC/plugins/enabled` to maintain enabled 74 | # plugin references instead of MicroDrop profile `microdrop.ini` 75 | app = get_app() 76 | self.update() 77 | response = self.window.run() 78 | self.window.hide() 79 | for p in self.controller.plugins: 80 | package_name = p.get_plugin_info().package_name 81 | # Extract importable Python module name from Conda package name. 82 | # 83 | # XXX Plugins are currently Python modules, which means that the 84 | # installed plugin directory must be a valid module name. However, 85 | # Conda package name conventions may include `.` and `-` 86 | # characters. 87 | module_name = package_name.split('.')[-1].replace('-', '_') 88 | if p.enabled(): 89 | if module_name not in app.config["plugins"]["enabled"]: 90 | app.config["plugins"]["enabled"].append(module_name) 91 | else: 92 | if module_name in app.config["plugins"]["enabled"]: 93 | app.config["plugins"]["enabled"].remove(module_name) 94 | app.config.save() 95 | if self.controller.restart_required: 96 | _L().warning('\n'.join(['Plugins and/or dependencies were ' 97 | 'installed/uninstalled.', 98 | 'Program needs to be restarted for ' 99 | 'changes to take effect.'])) 100 | # Use return code of `5` to signal program should be restarted. 101 | app.main_window_controller.on_destroy(None, return_code=5) 102 | return response 103 | return response 104 | 105 | 106 | if __name__ == '__main__': 107 | pm = PluginManagerDialog() 108 | -------------------------------------------------------------------------------- /microdrop/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | 4 | from plugin_manager import ILoggingPlugin 5 | import plugin_manager 6 | 7 | class CustomHandler(logging.Handler): 8 | def __init__(self): 9 | # run the regular Handler __init__ 10 | logging.Handler.__init__(self) 11 | 12 | def emit(self, record): 13 | if record.levelname == 'DEBUG': 14 | plugin_manager.emit_signal('on_debug', [record], interface=ILoggingPlugin) 15 | elif record.levelname == 'INFO': 16 | plugin_manager.emit_signal('on_info', [record], interface=ILoggingPlugin) 17 | elif record.levelname == 'WARNING': 18 | plugin_manager.emit_signal('on_warning', [record], interface=ILoggingPlugin) 19 | elif record.levelname == 'ERROR': 20 | plugin_manager.emit_signal('on_error', [record], interface=ILoggingPlugin) 21 | elif record.levelname == 'CRITICAL': 22 | plugin_manager.emit_signal('on_critical', [record], interface=ILoggingPlugin) 23 | 24 | logger = logging.getLogger() 25 | -------------------------------------------------------------------------------- /microdrop/microdrop.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/microdrop.ico -------------------------------------------------------------------------------- /microdrop/microdrop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import platform 4 | import sys 5 | import traceback 6 | 7 | 8 | # Disable Intel Fortran default console event handler. 9 | # 10 | # Without doing this, [`Control-C` causes abrupt termination with the following 11 | # message][i905]: 12 | # 13 | # forrtl: error (200): program aborting due to control-C event 14 | # 15 | # [i905]: https://github.com/ContinuumIO/anaconda-issues/issues/905#issuecomment-330678890 16 | #: ..versionadded:: 2.15.2 17 | os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1' 18 | 19 | 20 | if platform.system() == 'Windows': 21 | # When loading Portable MicroDrop on Windows 8.1, the following error 22 | # occurs when trying to import `win32com`, etc.: 23 | # 24 | # ImportError: DLL load failed: The specified module could not be 25 | # found. 26 | # 27 | # Importing `pythoncom` *(as done below)* prior to `win32com`, etc. seems 28 | # to work around the issue. 29 | # See ticket #174. 30 | import pythoncom 31 | 32 | 33 | def except_handler(*args, **kwargs): 34 | print args, kwargs 35 | traceback.print_tb(args[2]) 36 | 37 | 38 | def initialize_core_plugins(): 39 | # These imports automatically load (and initialize) core singleton plugins. 40 | from .core_plugins import zmq_hub_plugin 41 | from .core_plugins import command_plugin 42 | from .core_plugins import device_info_plugin 43 | from .core_plugins.electrode_controller_plugin import pyutilib 44 | from .core_plugins import prompt_plugin 45 | from .gui import experiment_log_controller 46 | from .gui import config_controller 47 | from .gui import main_window_controller 48 | from .gui import dmf_device_controller 49 | from .core_plugins import protocol_controller 50 | from .gui import protocol_grid_controller 51 | from .gui import plugin_manager_controller 52 | from .gui import app_options_controller 53 | 54 | 55 | def main(): 56 | import logging 57 | 58 | import gtk 59 | 60 | settings = gtk.settings_get_default() 61 | # Use a button ordering more consistent with Windows 62 | print 'Use a button ordering more consistent with Windows' 63 | settings.set_property('gtk-alternative-button-order', True) 64 | 65 | logging.basicConfig(format='%(asctime)s.%(msecs)03d [%(levelname)s:%(name)s]: ' 66 | '%(message)s', datefmt=r'%Y-%m-%d %H:%M:%S', 67 | level=logging.INFO) 68 | 69 | initialize_core_plugins() 70 | 71 | # XXX Import from `app` module automatically instantiates instance of `App` 72 | # class. 73 | from app import App 74 | from app_context import get_app 75 | 76 | gtk.threads_init() 77 | gtk.gdk.threads_init() 78 | my_app = get_app() 79 | sys.excepthook = except_handler 80 | my_app.run() 81 | 82 | 83 | if __name__ == "__main__": 84 | import multiprocessing 85 | 86 | import matplotlib 87 | 88 | matplotlib.use('Agg') 89 | multiprocessing.freeze_support() 90 | 91 | main() 92 | -------------------------------------------------------------------------------- /microdrop/static/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 70 | 74 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /microdrop/static/images/microdrop-environment-shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/microdrop-environment-shortcut.png -------------------------------------------------------------------------------- /microdrop/static/images/microdrop-plugin-manager-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/microdrop-plugin-manager-annotated.png -------------------------------------------------------------------------------- /microdrop/static/images/microdrop-shortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/microdrop-shortcuts.png -------------------------------------------------------------------------------- /microdrop/static/images/plugin-manager-install-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/plugin-manager-install-dependencies.png -------------------------------------------------------------------------------- /microdrop/static/images/plugin-manager-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/plugin-manager-remove.png -------------------------------------------------------------------------------- /microdrop/static/images/plugins-install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/static/images/plugins-install.gif -------------------------------------------------------------------------------- /microdrop/static/notebooks/Assign chip channels.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 37, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os\n", 12 | "import re\n", 13 | "\n", 14 | "from lxml import etree\n", 15 | "from path_helpers import path\n", 16 | "import svg_model" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "# Set path to device SVG file" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 38, 29 | "metadata": { 30 | "collapsed": true 31 | }, 32 | "outputs": [], 33 | "source": [ 34 | "# device_path = path(r'\\device.svg')" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "# Open device directory in file manager" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": { 48 | "collapsed": false 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "os.startfile(device_path.parent)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "# Assign `data-channels` attr of electrodes `svg:path` based on electrode index" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 39, 65 | "metadata": { 66 | "collapsed": true 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "cre_index = re.compile(r'electrode(?P\\d+)')\n", 71 | "xml_root = etree.parse(device_path)\n", 72 | "\n", 73 | "electrode_xpath = '//svg:g[@inkscape:label=\"Device\"]/svg:path[starts-with(@id, \"electrode\")]'\n", 74 | "\n", 75 | "# Assign `data-channels` attribute of each electrode `svg:path` based on electrode index.\n", 76 | "for electrode_i in xml_root.xpath(electrode_xpath, namespaces=svg_model.INKSCAPE_NSMAP):\n", 77 | " match = cre_index.match(electrode_i.attrib['id'])\n", 78 | " electrode_i.attrib['data-channels'] = str(int(match.group('index')))\n", 79 | "\n", 80 | "# Rename original file to `.original.svg`\n", 81 | "device_path.rename('%s.original.svg' % device_path.namebase)\n", 82 | "\n", 83 | "# Write result to original path.\n", 84 | "with device_path.open('wb') as output:\n", 85 | " output.write(etree.tounicode(xml_root))" 86 | ] 87 | } 88 | ], 89 | "metadata": { 90 | "kernelspec": { 91 | "display_name": "Python 2", 92 | "language": "python", 93 | "name": "python2" 94 | }, 95 | "language_info": { 96 | "codemirror_mode": { 97 | "name": "ipython", 98 | "version": 2 99 | }, 100 | "file_extension": ".py", 101 | "mimetype": "text/x-python", 102 | "name": "python", 103 | "nbconvert_exporter": "python", 104 | "pygments_lexer": "ipython2", 105 | "version": "2.7.9" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 0 110 | } 111 | -------------------------------------------------------------------------------- /microdrop/static/notebooks/Experiment log explorer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:58188a6da505e6bc689a072c14ab60ed4e52b4078dde58566b9a5b1fb0aa2ae6" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "--------------------------------------------------" 16 | ] 17 | }, 18 | { 19 | "cell_type": "heading", 20 | "level": 1, 21 | "metadata": {}, 22 | "source": [ 23 | "Experiment log explorer" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "collapsed": false, 29 | "input": [ 30 | "%matplotlib inline\n", 31 | "import cPickle as pickle\n", 32 | "from IPython.html.widgets import interact, IntSliderWidget, DropdownWidget\n", 33 | "from IPython.display import display, HTML\n", 34 | "\n", 35 | "import matplotlib.pyplot as plt\n", 36 | "import pandas as pd\n", 37 | "import numpy as np\n", 38 | "from path_helpers import path\n", 39 | "\n", 40 | "# Load experiment log data into `log` variable.\n", 41 | "log_root = path('.').abspath()\n", 42 | "log_path = log_root.joinpath('data')\n", 43 | "log = log_path.pickle_load()\n", 44 | "print 'Read log data from: %s' % log_path\n", 45 | "\n", 46 | "# Create widgets that we'll display below to navigate log data.\n", 47 | "def index_selected(name, index):\n", 48 | " dropdown.values = dict([(k, pickle.loads(v))\n", 49 | " for k, v in log.data[index].iteritems()])\n", 50 | " \n", 51 | "index_widget = IntSliderWidget(min=0, max=len(log.data) - 1)\n", 52 | "index_widget.value = index_widget.min\n", 53 | "# Update the plugin dropdown list whenever log index is changed.\n", 54 | "index_widget.on_trait_change(index_selected, 'value')\n", 55 | "\n", 56 | "dropdown = DropdownWidget(label='Plugin')\n", 57 | "index_selected('value', index_widget.value)" 58 | ], 59 | "language": "python", 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "output_type": "stream", 64 | "stream": "stdout", 65 | "text": [ 66 | "[interfaces] <_MainThread(MainThread, started 139882913400640)>\n", 67 | "Read log data from: /home/christian/.microdrop/devices/DMF-90-pin-array/logs/551/data\n" 68 | ] 69 | } 70 | ], 71 | "prompt_number": 1 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "--------------------------------------------------" 78 | ] 79 | }, 80 | { 81 | "cell_type": "heading", 82 | "level": 2, 83 | "metadata": {}, 84 | "source": [ 85 | "Explore" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "`log.data` is a list containing log data for each step. Note that the first\n", 93 | "entry (i.e., at index 0) does not correspond to a step, but instead contains\n", 94 | "details about hardware and software versions used for the experiment.\n", 95 | "\n", 96 | "Each log entry contains a dictionary mapping each plugin name to its corresponding\n", 97 | "data for the step.\n", 98 | "\n", 99 | "The data for each plugin is [serialized][1] using the Python [`pickle`][2] library.\n", 100 | "To load the step data for plugin, we can \"unpickle\" the corresponding dictionary\n", 101 | "entry.\n", 102 | "\n", 103 | "For example, the interactive demo below can be used to load and display the data\n", 104 | "for each log entry by plugin name. Drag the slider to change the entry index.\n", 105 | "\n", 106 | "[1]: http://en.wikipedia.org/wiki/Serialization\n", 107 | "[2]: https://docs.python.org/2/library/pickle.html" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "collapsed": true, 113 | "input": [ 114 | "import pprint\n", 115 | "\n", 116 | "\n", 117 | "def summarize(data):\n", 118 | " summary = pprint.pformat(data)\n", 119 | " if len(summary) > 1000:\n", 120 | " summary = summary[:1000] + '...'\n", 121 | " return summary\n", 122 | " \n", 123 | "\n", 124 | "def display_plugin_names(index=0):\n", 125 | " data_i = log.data[index]\n", 126 | " for plugin_name, plugin_pickled_data in data_i.iteritems():\n", 127 | " display(HTML('

%s

' % plugin_name))\n", 128 | " plugin_data = pickle.loads(plugin_pickled_data)\n", 129 | " print summarize(plugin_data)\n", 130 | " \n", 131 | " \n", 132 | "interact_function = interact(display_plugin_names, index=(0, len(log.data) - 1))" 133 | ], 134 | "language": "python", 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "html": [ 139 | "

core

" 140 | ], 141 | "metadata": {}, 142 | "output_type": "display_data", 143 | "text": [ 144 | "" 145 | ] 146 | }, 147 | { 148 | "output_type": "stream", 149 | "stream": "stdout", 150 | "text": [ 151 | "{'control board hardware version': '2.1',\n", 152 | " 'control board name': 'Arduino DMF Controller',\n", 153 | " 'control board serial number': 2,\n", 154 | " 'control board software version': '1.0.65-async-feedback',\n", 155 | " 'i2c devices': {10: '?',\n", 156 | " 32: 'HV Switching Board v2.1 (Firmware v0.2.54-pwm, S/N 4294967295)'},\n", 157 | " 'start time': 1431358960.857258}\n" 158 | ] 159 | } 160 | ], 161 | "prompt_number": 2 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "--------------------------------------------------" 168 | ] 169 | }, 170 | { 171 | "cell_type": "heading", 172 | "level": 2, 173 | "metadata": {}, 174 | "source": [ 175 | "Select" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "We saw above how to navigate through summaries of the experiment log data, but\n", 183 | "we can also select data for further analysis.\n", 184 | "\n", 185 | "Using the widgets below, the data for a particular plugin at a chosen log index\n", 186 | "may be chosen. The data is then available through the `value` attribute of the\n", 187 | "`dropdown` variable." 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "collapsed": false, 193 | "input": [ 194 | "def display_data(plugin_data=None):\n", 195 | " print summarize(plugin_data)\n", 196 | " return plugin_data\n", 197 | "\n", 198 | "display(index_widget)\n", 199 | "interact(display_data, plugin_data=dropdown)\n", 200 | "pass" 201 | ], 202 | "language": "python", 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "output_type": "stream", 207 | "stream": "stdout", 208 | "text": [ 209 | "{'control board hardware version': '2.1',\n", 210 | " 'control board name': 'Arduino DMF Controller',\n", 211 | " 'control board serial number': 2,\n", 212 | " 'control board software version': '1.0.65-async-feedback',\n", 213 | " 'i2c devices': {10: '?',\n", 214 | " 32: 'HV Switching Board v2.1 (Firmware v0.2.54-pwm, S/N 4294967295)'},\n", 215 | " 'start time': 1431358960.857258}\n" 216 | ] 217 | } 218 | ], 219 | "prompt_number": 3 220 | }, 221 | { 222 | "cell_type": "code", 223 | "collapsed": false, 224 | "input": [ 225 | "dropdown.value" 226 | ], 227 | "language": "python", 228 | "metadata": {}, 229 | "outputs": [ 230 | { 231 | "metadata": {}, 232 | "output_type": "pyout", 233 | "prompt_number": 4, 234 | "text": [ 235 | "{'control board hardware version': '2.1',\n", 236 | " 'control board name': 'Arduino DMF Controller',\n", 237 | " 'control board serial number': 2,\n", 238 | " 'control board software version': '1.0.65-async-feedback',\n", 239 | " 'i2c devices': {10: '?',\n", 240 | " 32: 'HV Switching Board v2.1 (Firmware v0.2.54-pwm, S/N 4294967295)'},\n", 241 | " 'start time': 1431358960.857258}" 242 | ] 243 | } 244 | ], 245 | "prompt_number": 4 246 | } 247 | ], 248 | "metadata": {} 249 | } 250 | ] 251 | } -------------------------------------------------------------------------------- /microdrop/support/gen_py/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/support/gen_py/__init__.py -------------------------------------------------------------------------------- /microdrop/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/__init__.py -------------------------------------------------------------------------------- /microdrop/tests/buildbot/master.cfg: -------------------------------------------------------------------------------- 1 | # -*- python -*- 2 | # ex: set syntax=python: 3 | 4 | # This is a sample buildmaster config file. It must be installed as 5 | # 'master.cfg' in your buildmaster's base directory. 6 | 7 | # This is the dictionary that the buildmaster pays attention to. We also use 8 | # a shorter alias to save typing. 9 | c = BuildmasterConfig = {} 10 | 11 | ####### BUILDSLAVES 12 | 13 | # The 'slaves' list defines the set of recognized buildslaves. Each element is 14 | # a BuildSlave object, specifying a unique slave name and password. The same 15 | # slave name and password must be configured on the slave. 16 | from buildbot.buildslave import BuildSlave 17 | c['slaves'] = [BuildSlave("ryan-desktop", "password"), 18 | BuildSlave("young", "password")] 19 | 20 | # 'slavePortnum' defines the TCP port to listen on for connections from slaves. 21 | # This must match the value configured into the buildslaves (with their 22 | # --master option) 23 | c['slavePortnum'] = 9989 24 | 25 | ####### CHANGESOURCES 26 | 27 | # the 'change_source' setting tells the buildmaster how it should find out 28 | # about source code changes. Here we point to the buildbot clone of pyflakes. 29 | 30 | #from buildbot.changes.gitpoller import GitPoller 31 | #c['change_source'] = GitPoller( 32 | # 'git://microfluidics.utoronto.ca/microdrop.git', 33 | # workdir='gitpoller-workdir', branch='master', 34 | # pollinterval=300) 35 | from buildbot.changes.pb import PBChangeSource 36 | c['change_source'] = PBChangeSource() 37 | 38 | ####### SCHEDULERS 39 | 40 | # Configure the Schedulers, which decide how to react to incoming changes. In this 41 | # case, just kick off a 'runtests' build 42 | 43 | from buildbot.schedulers.basic import SingleBranchScheduler 44 | from buildbot.changes import filter 45 | c['schedulers'] = [] 46 | c['schedulers'].append(SingleBranchScheduler( 47 | name="all", 48 | change_filter=filter.ChangeFilter(branch='master'), 49 | treeStableTimer=None, 50 | builderNames=["Win7,Py2.7", "WinXP,Py2.7"])) 51 | 52 | ####### BUILDERS 53 | 54 | # The 'builders' list defines the Builders, which tell Buildbot how to perform a build: 55 | # what steps, and which slaves can execute them. Note that any particular build will 56 | # only take place on one slave. 57 | 58 | from buildbot.process.factory import BuildFactory 59 | from buildbot.steps.source import Git 60 | from buildbot.steps.shell import ShellCommand 61 | from buildbot.steps.python import PyLint 62 | 63 | factory = BuildFactory() 64 | # check out the source 65 | factory.addStep(Git(repourl='git://microfluidics.utoronto.ca/microdrop.git', mode='copy', submodules=True)) 66 | factory.addStep(ShellCommand(command=["python microdrop/tests/update_dmf_control_board.py"])) 67 | factory.addStep(ShellCommand(command=["cd microdrop/plugins/dmf_control_board & scons"])) 68 | factory.addStep(ShellCommand(command=["nosetests", "--with-path=microdrop", "-vv"])) 69 | factory.addStep(ShellCommand(command=["python install_dependencies.py"])) 70 | factory.addStep(ShellCommand(command=["scons", "--wix-sval"])) 71 | factory.addStep(PyLint(command=["pylint", "--output-format=parseable", "microdrop"], flunkOnFailure=False)) 72 | 73 | from buildbot.config import BuilderConfig 74 | 75 | c['builders'] = [] 76 | c['builders'].append( 77 | BuilderConfig(name="Win7,Py2.7", 78 | slavenames=["ryan-desktop"], 79 | factory=factory)) 80 | c['builders'].append( 81 | BuilderConfig(name="WinXP,Py2.7", 82 | slavenames=["young"], 83 | factory=factory)) 84 | 85 | ####### STATUS TARGETS 86 | 87 | # 'status' is a list of Status Targets. The results of each build will be 88 | # pushed to these targets. buildbot/status/*.py has a variety to choose from, 89 | # including web pages, email senders, and IRC bots. 90 | 91 | c['status'] = [] 92 | 93 | from buildbot.status import html 94 | from buildbot.status.web import authz 95 | authz_cfg=authz.Authz( 96 | # change any of these to True to enable; see the manual for more 97 | # options 98 | gracefulShutdown = False, 99 | forceBuild = True, # use this to test your slave once it is set up 100 | forceAllBuilds = False, 101 | pingBuilder = False, 102 | stopBuild = False, 103 | stopAllBuilds = False, 104 | cancelPendingBuild = False, 105 | ) 106 | c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg)) 107 | 108 | ####### PROJECT IDENTITY 109 | 110 | # the 'title' string will appear at the top of this buildbot 111 | # installation's html.WebStatus home page (linked to the 112 | # 'titleURL') and is embedded in the title of the waterfall HTML page. 113 | 114 | c['title'] = "Microdrop" 115 | c['titleURL'] = "http://microfluidics.utoronto.ca/microdrop" 116 | 117 | # the 'buildbotURL' string should point to the location where the buildbot's 118 | # internal web server (usually the html.WebStatus page) is visible. This 119 | # typically uses the port number set in the Waterfall 'status' entry, but 120 | # with an externally-visible host name which the buildbot cannot figure out 121 | # without some help. 122 | 123 | c['buildbotURL'] = "http:///microfluidics.utoronto.ca/buildbot/" 124 | 125 | ####### DB URL 126 | 127 | # This specifies what database buildbot uses to store change and scheduler 128 | # state. You can leave this at its default for all but the largest 129 | # installations. 130 | c['db_url'] = "sqlite:///state.sqlite" 131 | 132 | from buildbot.status.mail import MailNotifier 133 | mn = MailNotifier(mode='failing', 134 | fromaddr='wheeler.lab.server@gmail.com', 135 | sendToInterestedUsers=True, 136 | extraRecipients=['microdrop-dev@googlegroups.com'], 137 | useTls=True, relayhost="smtp.gmail.com", smtpPort=587, 138 | smtpUser="wheeler.lab.server@gmail.com", smtpPassword='password') 139 | c['status'].append(mn) 140 | -------------------------------------------------------------------------------- /microdrop/tests/devices/device 0 v0.2.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/devices/device 0 v0.2.0 -------------------------------------------------------------------------------- /microdrop/tests/devices/device 0 v0.3.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/devices/device 0 v0.3.0 -------------------------------------------------------------------------------- /microdrop/tests/devices/device 1 v0.2.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/devices/device 1 v0.2.0 -------------------------------------------------------------------------------- /microdrop/tests/devices/device 1 v0.3.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/devices/device 1 v0.3.0 -------------------------------------------------------------------------------- /microdrop/tests/experiment_logs/experiment log 0 v0.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/microdrop/tests/experiment_logs/experiment log 0 v0.0.0 -------------------------------------------------------------------------------- /microdrop/tests/protocols/protocol 0 v0.0.0: -------------------------------------------------------------------------------- 1 | !!python/object:protocol.Protocol 2 | current_repetition: 0 3 | current_step_number: 1 4 | n_repeats: 1 5 | name: version0 6 | plugin_data: {} 7 | plugin_fields: {} 8 | steps: 9 | - !!python/object:protocol.Step 10 | plugin_data: 11 | microdrop.gui.dmf_device_controller: !!python/object:gui.dmf_device_controller.DmfDeviceOptions 12 | state_of_channels: !!python/object/apply:numpy.core.multiarray._reconstruct 13 | args: 14 | - &id001 !!python/name:numpy.ndarray '' 15 | - !!python/tuple [0] 16 | - b 17 | state: !!python/tuple 18 | - 1 19 | - !!python/tuple [120] 20 | - !!python/object/apply:numpy.dtype 21 | args: [f8, 0, 1] 22 | state: !!python/tuple [3, <, null, null, null, -1, -1, 0] 23 | - false 24 | - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 25 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 26 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 27 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 28 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 29 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 30 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 31 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 32 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 33 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 34 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 35 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 36 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 37 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 38 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 39 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 40 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 41 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 42 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 43 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 44 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 45 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 46 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 47 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 48 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 49 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 50 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 51 | wheelerlab.dmf_control_board_1.2: !!python/object:plugins.dmf_control_board.microdrop.DmfControlBoardOptions 52 | duration: 100 53 | feedback_options: !!python/object:plugins.dmf_control_board.microdrop.feedback.FeedbackOptions 54 | action: !!python/object:plugins.dmf_control_board.microdrop.feedback.RetryAction { 55 | capacitance_threshold: 0, increase_voltage: 0, max_repeats: 3} 56 | delay_between_samples_ms: 0 57 | feedback_enabled: false 58 | n_samples: 10 59 | sampling_time_ms: 10 60 | frequency: 1000.0 61 | voltage: 100 62 | - !!python/object:protocol.Step 63 | plugin_data: 64 | microdrop.gui.dmf_device_controller: !!python/object:gui.dmf_device_controller.DmfDeviceOptions 65 | state_of_channels: !!python/object/apply:numpy.core.multiarray._reconstruct 66 | args: 67 | - *id001 68 | - !!python/tuple [0] 69 | - b 70 | state: !!python/tuple 71 | - 1 72 | - !!python/tuple [120] 73 | - !!python/object/apply:numpy.dtype 74 | args: [f8, 0, 1] 75 | state: !!python/tuple [3, <, null, null, null, -1, -1, 0] 76 | - false 77 | - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 78 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 79 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 80 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 81 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 82 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 83 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 84 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 85 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 86 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 87 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 88 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 89 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 90 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 91 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 92 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 93 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 94 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 95 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 96 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 97 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 98 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 99 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 100 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 101 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 102 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ 103 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 104 | wheelerlab.dmf_control_board_1.2: !!python/object:plugins.dmf_control_board.microdrop.DmfControlBoardOptions 105 | duration: 100 106 | feedback_options: !!python/object:plugins.dmf_control_board.microdrop.feedback.FeedbackOptions 107 | action: !!python/object:plugins.dmf_control_board.microdrop.feedback.RetryAction { 108 | capacitance_threshold: 0, increase_voltage: 0, max_repeats: 3} 109 | delay_between_samples_ms: 0 110 | feedback_enabled: true 111 | n_samples: 10 112 | sampling_time_ms: 10 113 | frequency: 1000 114 | voltage: 100 115 | version: 0.0.0 116 | -------------------------------------------------------------------------------- /microdrop/tests/test_dmf_device.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from path_helpers import path 4 | from nose.tools import raises, eq_ 5 | 6 | from dmf_device import DmfDevice 7 | from microdrop_utility import Version 8 | from svg_model.svgload.svg_parser import SvgParser, parse_warning 9 | from svg_model.path_group import PathGroup 10 | 11 | 12 | def test_load_dmf_device(): 13 | """ 14 | test loading DMF device files 15 | """ 16 | 17 | # version 0.2.0 files 18 | for i in [0, 1]: 19 | yield load_device, (path(__file__).parent / 20 | path('devices') / 21 | path('device %d v%s' % (i, Version(0,2,0)))) 22 | 23 | # version 0.3.0 files 24 | for i in [0, 1]: 25 | yield load_device, (path(__file__).parent / 26 | path('devices') / 27 | path('device %d v%s' % (i, Version(0,3,0)))) 28 | 29 | 30 | def load_device(name): 31 | DmfDevice.load(name) 32 | assert True 33 | 34 | 35 | @raises(IOError) 36 | def test_load_non_existant_dmf_device(): 37 | """ 38 | test loading DMF device file that doesn't exist 39 | """ 40 | DmfDevice.load(path(__file__).parent / 41 | path('devices') / 42 | path('no device')) 43 | 44 | 45 | def _svg_parse(i, expected_paths_count): 46 | root = path(__file__).parent 47 | svg_parser = SvgParser() 48 | svg_path = root.joinpath('svg_files', 'test_device_%d.svg' % i) 49 | #with Silence(): 50 | svg = svg_parser.parse_file(svg_path, on_error=parse_warning) 51 | eq_(len(svg.paths), expected_paths_count) 52 | time.sleep(1.0) 53 | 54 | 55 | def _import_device(i, root): 56 | svg_path = root.joinpath('svg_files', 'test_device_%d.svg' % i) 57 | device = DmfDevice.load_svg(svg_path) 58 | time.sleep(1.0) 59 | 60 | 61 | def test_svg_parser(): 62 | expected_paths_count = [72, 57, 56, 131, 106] 63 | for i in range(5): 64 | yield _svg_parse, i, expected_paths_count[i] 65 | 66 | 67 | def test_import_device(root=None): 68 | if root is None: 69 | root = path(__file__).parent 70 | else: 71 | root = path(root) 72 | for i in range(6): 73 | yield _import_device, i, root 74 | -------------------------------------------------------------------------------- /microdrop/tests/test_experiment_log.py: -------------------------------------------------------------------------------- 1 | from path_helpers import path 2 | from nose.tools import raises 3 | 4 | from experiment_log import ExperimentLog 5 | from microdrop_utility import Version 6 | 7 | def test_load_experiment_log(): 8 | """ 9 | test loading experiment log files 10 | """ 11 | 12 | # version 0.0.0 files 13 | for i in [0]: 14 | yield load_experiment_log, (path(__file__).parent / 15 | path('experiment_logs') / 16 | path('experiment log %d v%s' % (i, Version(0,0,0)))) 17 | 18 | # version 0.1.0 files 19 | for i in [0]: 20 | yield load_experiment_log, (path(__file__).parent / 21 | path('experiment_logs') / 22 | path('experiment log %d v%s' % (i, Version(0,1,0)))) 23 | 24 | def load_experiment_log(name): 25 | ExperimentLog.load(name) 26 | assert True 27 | 28 | 29 | @raises(IOError) 30 | def test_load_non_existant_experiment_log(): 31 | """ 32 | test loading experiment log file that doesn't exist 33 | """ 34 | ExperimentLog.load(path(__file__).parent / 35 | path('experiment_logs') / 36 | path('no log')) 37 | -------------------------------------------------------------------------------- /microdrop/tests/test_protocol.py: -------------------------------------------------------------------------------- 1 | from path_helpers import path 2 | from nose.tools import raises 3 | 4 | from protocol import Protocol 5 | from microdrop_utility import Version 6 | 7 | def test_load_protocol(): 8 | """ 9 | test loading protocol files 10 | """ 11 | 12 | # version 0.0.0 files 13 | for i in [0]: 14 | yield load_protocol, (path(__file__).parent / 15 | path('protocols') / 16 | path('protocol %d v%s' % (i, Version(0,0,0)))) 17 | 18 | # version 0.1.0 files 19 | for i in [0]: 20 | yield load_protocol, (path(__file__).parent / 21 | path('protocols') / 22 | path('protocol %d v%s' % (i, Version(0,1,0)))) 23 | 24 | 25 | def load_protocol(name): 26 | Protocol.load(name) 27 | assert True 28 | 29 | 30 | @raises(IOError) 31 | def test_load_non_existant_protocol(): 32 | """ 33 | test loading protocol file that doesn't exist 34 | """ 35 | Protocol.load(path(__file__).parent / 36 | path('protocols') / 37 | path('no protocol')) 38 | -------------------------------------------------------------------------------- /microdrop/tests/update_dmf_control_board.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | if __name__ == '__main__': 6 | os.chdir('microdrop/plugins') 7 | try: 8 | if not os.path.exists('dmf_control_board'): 9 | print 'Clone dmf_control_board repository...' 10 | print subprocess.check_output(['git', 'clone', 11 | 'http://microfluidics.utoronto.ca/git/dmf_control_board.git'], 12 | stderr=subprocess.STDOUT) 13 | else: 14 | print 'Fetch lastest dmf_control_board update...' 15 | print subprocess.check_output(['git', 'pull'], 16 | stderr=subprocess.STDOUT) 17 | sys.exit(0) 18 | except subprocess.CalledProcessError, e: 19 | print e.output 20 | sys.exit(e.returncode) -------------------------------------------------------------------------------- /pavement.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pkg_resources 3 | import platform 4 | import sys 5 | 6 | from paver.easy import task, needs, path 7 | from paver.setuputils import setup 8 | 9 | import versioneer 10 | 11 | 12 | install_requires = ['application_repository>=0.5', 'blinker', 'configobj', 13 | 'droplet-planning>=0.2', 'flatland-fork', 'geo-util', 14 | 'ipython', 'jupyter-helpers', 'jinja2', 15 | 'matplotlib>=1.5.0', 16 | 'microdrop-device-converter>=0.1.post5', 17 | 'microdrop-plugin-template>=1.1.post30', 18 | 'microdrop_utility>=0.4.post2', 'networkx', 19 | 'openpyxl', 'pandas>=0.17.1', 'path-helpers>=0.2.post4', 20 | 'paver>=1.2.4', 'pip-helpers>=0.6', 21 | 'pygtk_textbuffer_with_undo', 'pyparsing', 22 | 'pyutilib.component.core>=4.4.1', 23 | 'pyutilib.component.loader>=3.3.1', 'pyyaml', 'pyzmq', 24 | 'run-exe>=0.5', 'si-prefix>=0.4.post10', 'scipy', 25 | 'svgwrite', 'svg-model>=0.6', 'task_scheduler', 26 | 'tornado', 'wheeler.pygtkhelpers>=0.16', 27 | 'zmq-plugin>=0.2.post2'] 28 | 29 | if platform.system() == 'Windows': 30 | install_requires += ['pycairo-gtk2-win', 'pywin32'] 31 | else: 32 | try: 33 | import gtk 34 | except ImportError: 35 | print >> sys.stderr, ("Please install Python bindings for Gtk 2 using " 36 | "your system's package manager.") 37 | try: 38 | import cairo 39 | except ImportError: 40 | print >> sys.stderr, ("Please install Python bindings for cairo using " 41 | "your system's package manager.") 42 | 43 | 44 | setup(name='microdrop', 45 | version=versioneer.get_version(), 46 | cmdclass=versioneer.get_cmdclass(), 47 | description='MicroDrop is a graphical user interface for the DropBot ' 48 | 'Digital Microfluidics control system', 49 | keywords='digital microfluidics dmf automation dropbot microdrop', 50 | author='Ryan Fobel and Christian Fobel', 51 | author_email='ryan@fobel.net and christian@fobel.net', 52 | url='https://github.com/wheeler-microfluidics/microdrop', 53 | license='BSD', 54 | long_description='\n%s\n' % open('README.md', 'rt').read(), 55 | packages=['microdrop'], 56 | include_package_data=True, 57 | install_requires=install_requires, 58 | entry_points={'console_scripts': 59 | ['microdrop = microdrop.microdrop:main']}) 60 | 61 | 62 | @task 63 | def create_requirements(): 64 | package_list = [p.split('==')[0] for p in install_requires] 65 | requirements_path = os.path.join('microdrop', 'requirements.txt') 66 | with open(requirements_path, 'wb') as output: 67 | output.write('\n'.join(['%s==%s' % 68 | (p, pkg_resources.get_distribution(p).version) 69 | for p in package_list])) 70 | 71 | 72 | @task 73 | def build_installer(): 74 | try: 75 | import constructor_git as cg 76 | import constructor_git.__main__ 77 | except ImportError: 78 | print >> sys.stderr, ('`constructor-git` package not found. Install ' 79 | 'with `conda install constructor-git`.') 80 | raise SystemExit(-1) 81 | 82 | repo_path = path(__file__).parent.realpath() 83 | print repo_path 84 | cg.__main__.main(repo_path) 85 | 86 | 87 | @task 88 | @needs('generate_setup', 'minilib', 'create_requirements', 89 | 'setuptools.command.sdist') 90 | def sdist(): 91 | """Overrides sdist to make sure that our setup.py is generated.""" 92 | pass 93 | -------------------------------------------------------------------------------- /requirements-sphinx.txt: -------------------------------------------------------------------------------- 1 | application_repository>=0.5 2 | blinker 3 | configobj 4 | droplet-planning>=0.2 5 | flatland-fork 6 | geo-util 7 | ipython 8 | ipython-helpers>=0.4 9 | jinja2 10 | matplotlib>=1.5.0 11 | microdrop-device-converter>=0.1.post5 12 | microdrop_utility>=0.4.post2 13 | mock 14 | networkx 15 | numpydoc 16 | openpyxl 17 | pandas>=0.17.1 18 | path-helpers>=0.2.post4 19 | paver>=1.2.4 20 | pip-helpers>=0.6 21 | pygtk_textbuffer_with_undo 22 | pyparsing 23 | pyutilib.component.core>=4.4.1 24 | pyutilib.component.loader>=3.3.1 25 | pyyaml 26 | pyzmq 27 | run-exe>=0.5 28 | si-prefix>=0.4.post10 29 | scipy 30 | svgwrite 31 | svg-model>=0.6 32 | task_scheduler 33 | tornado 34 | wheeler.pygtkhelpers>=0.13.post17 35 | zmq-plugin>=0.2.post2 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | application_repository>=0.5 2 | blinker 3 | configobj 4 | droplet-planning>=0.2 5 | flatland-fork 6 | geo-util 7 | ipython 8 | ipython-helpers>=0.4 9 | jinja2 10 | matplotlib>=1.5.0 11 | microdrop-device-converter>=0.1.post5 12 | microdrop-plugin-template>=1.1.post30 13 | microdrop_utility>=0.4.post2 14 | networkx 15 | openpyxl 16 | pandas>=0.17.1 17 | path-helpers>=0.2.post4 18 | paver>=1.2.4 19 | pip-helpers>=0.6 20 | pygtk_textbuffer_with_undo 21 | pyparsing 22 | pyutilib.component.core>=4.4.1 23 | pyutilib.component.loader>=3.3.1 24 | pyyaml 25 | pyzmq 26 | run-exe>=0.5 27 | si-prefix>=0.4.post10 28 | scipy 29 | svgwrite 30 | svg-model>=0.6 31 | task_scheduler 32 | tornado 33 | wheeler.pygtkhelpers>=0.13.post17 34 | zmq-plugin>=0.2.post2 35 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # See the docstring in versioneer.py for instructions. Note that you must 2 | # re-run 'versioneer.py setup' after changing this section, and commit the 3 | # resulting files. 4 | 5 | [versioneer] 6 | VCS = git 7 | style = pep440 8 | versionfile_source = microdrop/_version.py 9 | versionfile_build = microdrop/_version.py 10 | tag_prefix = v 11 | parentdir_prefix = microdrop- 12 | -------------------------------------------------------------------------------- /setup_requirements.txt: -------------------------------------------------------------------------------- 1 | blinker 2 | http://microfluidics.utoronto.ca/git/python___path.git/snapshot/da43890764f1ee508fe6c32582acd69b87240365.zip 3 | -------------------------------------------------------------------------------- /site_scons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sci-bots/microdrop/11229fa85c71957594a30ccb6dd693c978074bc6/site_scons/__init__.py -------------------------------------------------------------------------------- /site_scons/git_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import Popen, PIPE, check_call, CalledProcessError 3 | import re 4 | 5 | from path_helpers import path 6 | 7 | 8 | class GitError(Exception): 9 | pass 10 | 11 | 12 | class GitUtil(object): 13 | def __init__(self, root_path='.'): 14 | self.root_path = path(root_path) 15 | if root_path is None: 16 | dir_node = path(os.getcwd()) 17 | while not dir_node.dirs('.git') and dir_node: 18 | dir_node = dir_node.parent 19 | if not dir_node: 20 | raise GitError('No git root found.') 21 | self.root_path = dir_node 22 | self._git = None 23 | assert((self.root_path / path('.git')).exists()) 24 | 25 | @property 26 | def git(self): 27 | if self._git: 28 | return self._git 29 | 30 | cmds = ['git'] 31 | if os.name == 'nt': 32 | exceptions = (WindowsError,) 33 | cmds += ['git.cmd'] 34 | else: 35 | exceptions = (OSError,) 36 | 37 | valid_cmd = False 38 | for cmd in cmds: 39 | try: 40 | check_call([cmd], stdout=PIPE, stderr=PIPE) 41 | except exceptions: 42 | # The command was not found, try the next one 43 | pass 44 | except CalledProcessError: 45 | valid_cmd = True 46 | break 47 | if not valid_cmd: 48 | raise GitError, 'No valid git command found' 49 | self._git = cmd 50 | return self._git 51 | 52 | def command(self, x): 53 | try: 54 | x.__iter__ 55 | except: 56 | x = re.split(r'\s+', x) 57 | cwd = os.getcwd() 58 | 59 | os.chdir(self.root_path) 60 | cmd = [self.git] + x 61 | stdout, stderr = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE).communicate() 62 | os.chdir(cwd) 63 | 64 | if stderr: 65 | raise GitError('Error executing git %s' % x) 66 | return stdout.strip() 67 | 68 | def describe(self): 69 | return self.command('describe') 70 | 71 | def summary(self, color=False): 72 | if color: 73 | format_ = '''--pretty=format:%Cred%h%Creset - %s %Cgreen(%cr)%Creset''' 74 | else: 75 | format_ = '''--pretty=format:%h - %s (%cr)''' 76 | return self.command(['''log''', '''--graph''', format_, 77 | '''--abbrev-commit''', '''--date=relative''']) 78 | 79 | def rev_parse(self, ref='HEAD'): 80 | return self.command(['rev-parse', ref]) 81 | 82 | def show(self, ref='HEAD', color=False, extra_args=None): 83 | extra_args = [extra_args, []][extra_args is None] 84 | args = ['show', ref] 85 | if color: 86 | args += ['--color'] 87 | return self.command(args + extra_args) 88 | -------------------------------------------------------------------------------- /site_scons/path_find.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from path_helpers import path 4 | 5 | 6 | def path_find(filename): 7 | for p in [path(d) for d in os.environ['PATH'].split(';')]: 8 | if p.isdir(): 9 | if len(p.files(filename)): 10 | return p 11 | return None 12 | --------------------------------------------------------------------------------