├── .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 | [][doc-status]
4 | [][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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------