Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (293)
Showing
with 656 additions and 1670 deletions
......@@ -6,7 +6,12 @@ Breakpoints_v2.xcbkptlist
*.pyc
*.pyo
*~
/dstatInterface/dist/
/dstatInterface/build/
/dstat-interface/dstat-interface/dist/
/dstat-interface/dstat-interface/build/
\ No newline at end of file
RELEASE-VERSION
paver-minilib.zip
dist
setup.py
*.egg-info
last_params
last_params.yml
\.idea/
File moved
Version 1.4.6
-Fixed data output for SWV/DPV forward/reverse current
-Working progress bars
-Refactored a lot of plotting code
-Calibration should work properly now
Version 1.4.5
-Made board definitions modular
-Fix several bugs with experiment parameters
-Uses DAC unit based parameters now (**REQUIRES dstat-firmware@9e4a9f or higher**)
-Change package import structure again (main must always be run as module now)
-Workaround for weird Gtk+3 redrawing bug on Windows
Version 1.4.4
-Make connection code more robust
-Execute button disabled until DStat is ready to start
-Supports new firmware version strings added in dstat-firmware@c5f9701
-Experimental firmware upgrade tool (see DStat Menu)
-Fix many bugs
Version 1.4.3
-Fix another critical bug with Windows multiprocessing
-Allow normal exit even if DStat was never connected
-Store last parameters in user folder
Version 1.4.2
-Refactor to fix critical bug preventing running packaged versions.
Version 1.4.1
-Fixed voltage axis orientation for LSV, CV, SWV, and DPV (Thanks to Dan Bizzotto @ UBC)
-Tweaked paver files to make version detection work without git.
Version 1.4
-Switched to GTK+3
-Support new DStat communications protocol (requires dstat-firmware>fe50c38)
-Many behind-the-scenes changes to improve code readability and make adding new experiment types easier
-Documented new Anaconda packages
Version 1.3.3
-Bugfix #24: Remove ZODB support until it can be fixed for latest ZODB
Version 1.3.2
-Improves initial connection reliability
Version 1.3.1
-Fixed electrochem modes broken when database added
-Make metadata keys optional.
Version 1.3
-Fixed a bug related to calibration mode
-Added ZODB data storage
-Integrated with zmq_plugin
Version 1.2
-Old Microdrop interface depreciated
-New zmq_plugin based interface
-Internal changes to save functionality and plot storage.
Version 1.1.3
-Changed internal storage of experiment data
-Added Analysis options:
-FFT integral moved there
-Basic statistics
Version 1.1.2
-Fixed more critical bugs from refactoring
Version 1.1.1
-Fixed critical bug that made PGA setting change with Gain resistor
Version 1.1
-Plot will be prettier if seaborn is installed
-Fixed bug in shutter FFT display
-Revamped experiment parameter system:
-Adds requirement for yaml
-Parameters automatically saved and loaded from last session
-Can manually save and load parameter files
Version 1.0.7
-Fixed a few bugs for systems without git
-Implements mean crossing detection instead of windowing for shutter FFT
Version 1.0.6
-Automatically integrates shutter FFT peak and saves to data file
-Adds option to offset start of FFT to avoid PMT startup delay
Version 1.0.5
-Bugfix for saving error introduced by new logging system
Version 1.0.4
-Adds support for synchronous electromechanical shutter detection (added in dstat/dstat-firmware@29d4c86)
-New version string system
Version 1.0.3
-Fixed #14: Added support for PMT idle mode
-Reduced CPU usage when running OCP by reducing polling frequency
Version 1.0.2a
-Hotfix #12: Restored measurement ability on Windows
-Minor logging changes
-Automatically enable TCS on DStat when measure light sensor button clicked.
Version 1.0.2
-Improved logging system: Log messages now print showing where they came from.
-Implemented gobject IO callbacks for experiments:
Process will not continuously poll for new data from serial process anymore.
-Stop button works again
-Buttons in Photodiode and Calibration modules remain insensitive until ready.
File moved
include RELEASE-VERSION
include version.py
include setup.py
include main.py
include paver-minilib.zip
include LICENSE
include CHANGELOG
include README.markdown
include core/utils/RELEASE-VERSION
recursive-include dstat_interface *
recursive-exclude dstat_interface *.pyc
recursive-exclude dstat_interface *~
recursive_exclude core last_params.yml
recursive-exclude . .DS_Store
\ No newline at end of file
##### _DStat is described in detail in [Dryden MDM, Wheeler AR (2015) DStat: A Versatile, Open-Source Potentiostat for Electroanalysis and Integration. PLoS ONE 10(10): e0140349. doi: 10.1371/journal.pone.0140349](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0140349) If you use this information in published work, please cite accordingly._
---
## Python 2.7 is now discontinued and gtk has always been a pain for cross-platform use, so I am in the process of writing a new interface in Python 3 and Qt that I hope will be working before too long.
This is the documentation for the DStat interface software.
The DStat interface is written primarily in Python and runs on Linux, Mac, and Windows.
It is the main method for running experiments on the DStat, controlling experimental parameters and collecting and plotting data.
It currently has no abilities for analyzing recorded data or opening previously saved data files, but data is saved in a simple text format or numpy-compatible binary format and plots can be saved as images.
## Table of Contents:
1. [Installation](#Installation)
1. [MacOS](#macos)
1. [Using Anaconda (Recommended)](#using-anaconda-recommended)
2. [Old Homebrew Instructions](#old-homebrew-instructions)
1. [Linux](#linux)
2. [Windows](#windows)
3. [Upgrading](#upgrading)
2. [Getting Started](#Getting-Started)
# Introduction
The DStat interface is written primarily in Python and runs on Linux, Mac, and Windows.
It is the main method for running experiments on the DStat, controlling experimental parameters and collecting and plotting data.
It currently has no abilities for analyzing recorded data or opening previously saved data files, but data is saved in a simple text format and plots can be saved as images.
# Installation
dstat-interface has moved to gtk+3 and we now recommend Anaconda/Miniconda for installation.
## MacOS
### Using Anaconda (Recommended)
1. [Install Miniconda](https://repo.continuum.io/miniconda/Miniconda2-latest-MacOSX-x86_64.sh) It doesn't matter if you pick Python 2.7 or 3.5—this just sets Miniconda's default Python. (Skip if Miniconda or Anaconda are already installed)
2. Download the [conda env file](conda-env.yml).
3. In the terminal, create the dstat environment (replacing </path/to/conda-env.yml> with the actual path to the file on your computer):
````conda env create -f </path/to/conda-env.yml>````
3. Then to run dstat-interface:
````source activate dstat
python -m dstat_interface.main````
#### Old Homebrew Instructions
The easiest way to get most of the necessary requirements to run dstat-interface is using [Homebrew](http://brew.sh):
```shell
brew tap homebrew/python
brew update
brew install python gobject-introspection gtk+3 pygobject3 py2cairo scipy zeromq
brew install matplotlib --with-pygtk
```
Be patient on the last step—matplotlib needs to be compiled and may take 2 or 3 minutes.
Make sure you're using brew-installed python, not OS X's default python. `which python` should point to `/usr/local/bin/python` not `/usr/bin/python`. Type `brew doctor` for more information if you are having issues.
The final requirements, can be installed using python's pip system:
pip install pandas pyserial pyzmq pyyaml seaborn zmq-plugin
## Linux
Linux prerequisite installation is similar to that of MacOS with Homebrew, only using your distribution's native package manager rather than Homebrew, and X11 will likely be installed already. Some distributions may not have packages available for installing matplotlib or numpy, in which case, they should be installed using pip.
These instructions were tested on Ubuntu 17.04:
````shell
sudo apt-get install gobject-introspection python-gobject python-pip
pip install dstat-interface
````
You will need to add your user to the `dialout` group to access virtual serial ports (replace <user> with your username):
```shell
sudo usermod -a -G dialout <user>
```
## Windows
The following terminal commands will result in a full installation of dstat-interface and its requirements, assuming [64-bit Miniconda][1] is installed:
```shell
conda create -n dstat -c mdryden python=2 dstat-interface
activate dstat
```
To finish the installation, GTK+3 and its Python bindings must be installed:
1. Download the latest all-in-one installer from [here][2].
2. When the installer prompts for the path to your Python distribution, navigate to your Miniconda/Anaconda envs folder and choose the dstat folder. If you chose to install Miniconda for your user only, the envs folder is found in `$USER/Miniconda2/envs` (for Miniconda) or `$USER/Anaconda/envs` (for Anaconda), where `$USER` is your user directory. If you installed Miniconda for all users, the Miniconda2/Anaconda folder will be in the root of your C: drive.
3. When the installer asks which modules to install, choose GTK.
4. Finish the installer.
We are installing in a separate environment to keep a clean system.
`activate dstat` will enter the environment (must be done whenever a new terminal is opened),
and `deactivate` will return to the root environment.
Therefore, to run dstat-interface from our environment, we must first activate it (if not already done) before launching it:
```shell
activate dstat
python -m dstat_interface.main
```
[1]: https://repo.continuum.io/miniconda/Miniconda2-latest-Windows-x86_64.exe
[2]: https://sourceforge.net/projects/pygobjectwin32/
## Upgrading
Anaconda builds can be upgraded to the latest version by issuing this command (from an activated conda environment):
```shell
conda upgrade -c mdryden dstat-interface # For MacOS, be sure to upgrade dstat-interface-deps as well
```
pip installs can be upgraded similarly:
```shell
pip install --upgrade dstat-interface
```
You can also run development builds directly from a cloned git repository (from an activated conda environment):
```shell
cd ~/src/dstat-interface/dstat_interface # Replace with path to dstat_interface folder inside repository
python -m main
```
# Getting started
## Interface overview
![Main interface](images/1.png)
1. Menu bar
![Menu bar](images/5.png)
* File
* Save Current data… — Saves the data of the currently visible plot as a space-separated text file or numpy .npy file
* Save Plot… – Save the currently visible plot as a .pdf
* Quit — Quits dstat-interface
* Dropbot
* Connect — Listens for µDrop connection over ZMQ
* Disconnect — Disconnect from µDrop
* Help
* About — Displays license information
2. ADC Settings Panel
* PGA Setting — Sets the ADC's internal voltage gain. Should almost always be left at 2x except for potentiometry as increasing voltage gain reduces S/N. Settings other than 2x do not adjust data to match (e.g. one must halve measured current/voltage values if PGA is set to 4x).
* Sample Rate - Sets the ADC's sample frequency. Lower rates give less noise, but reduced temporal resolution. Digital filter has a zero at multiples of the sample rate, so the Sample Rate setting can be used to filter out AC line noise by setting the rate to a factor of the line frequency, e.g. for 60 Hz rejection, choose 60, 30, 15, 10, 5, or 2.5 Hz.
* Input Buffer — Sets the ADC's internal input buffer. Should generally be enabled.
3. Potentiostat Settings Panel
* Gain — Controls the current-to-voltage converter gain. Higher values produce better S/N but reduce full scale current limit. Has no effect on potentiometry experiments.
4. Experiment Panel - Pulldown menu changes between experiment types and parameters are entered below.
5. Experiment Control
* Execute — Start the currently selected experiment with the given parameters.
* Stop — Stop the currently running experiment. If Autosave is enabled, the partial experiment will be saved.
6. Communications Panel
* Serial Port — Select the port where DStat is located. On Windows, this is generally something like `COM3`. On Mac OS X, it should appear as `/dev/cu.usbmodem12...E1`. On Linux, it may vary, but will start with `/dev`. If you're not sure, the simplest way is to check the list before and after plugging the DStat in. (Clicking the Refresh button after)
* Refresh — Refreshes the Serial Port list
* Connect — Attempts to handshake with DStat. If unsuccessful, it will time out after approximately 30 seconds.
* OCP — Displays the current open circuit potential measured at the reference electrode input. Active when DStat is connected and an experiment is not running.
* Status bar — Displays status and error messages.
7. Data display tabs — Switches between the plot and raw data tabs
* Plot — Displays the graphical representation of the incoming data.
* Raw Data — The raw experiment data. Doesn't appear until the experiment is complete. The first column corresponds to the x-axis of the plot (time or voltage), and the second column corresponds to the y-axis (current or voltage). For mult-scan experiments, additional pairs of columns represent successive scans.
![raw data](images/4.png)
* Extra Data — For SWV and DPV, the separate forward and reverse currents are recorded here.
8. Autosave controls
* Autosave — Enables automatic data saving on experiment completion. A text data file and a .pdf image of the plot will be saved.
* File save location
* File name selector — A number will be appended automatically if a file with the same name already exists.
9. Plot display
10. Plot navigation controls — For changing the view of the data plot.
## Connecting to DStat
1. Plug the DStat into a USB port, ensuring that drivers are loaded correctly on Windows systems.
2. Click Refresh in the Communications Panel to refresh the Serial Port list and choose the correct entry for the DStat (described above).
3. Click Connect.
4. If the connection was successful, a number should appear in the OCP field and the version number will appear in the status bar.
![connect](images/2.png)
If the connection failed, unplug the DStat and try again.
## Running an experiment
1. Choose the experiment you want to run in the Experiment Panel.
2. Fill the parameter fields.
3. Set an appropriate potentiostat gain.
4. Click Execute.
![experiment](images/3.png)
name: dstat
channels:
- conda-forge
- mdryden
- defaults
dependencies:
- ca-certificates=2018.4.16=0
- certifi=2018.4.16=py27_0
- nb_conda_kernels=2.1.0=py27_0
- openssl=1.0.2o=0
- appnope=0.1.0=py27_0
- backports=1.0=py27_0
- backports_abc=0.5=py27_0
- bleach=1.5.0=py27_0
- configparser=3.5.0=py27_0
- curl=7.54.1=0
- cycler=0.10.0=py27_0
- decorator=4.1.2=py27_0
- entrypoints=0.2.3=py27_0
- enum34=1.1.6=py27_0
- expat=2.1.0=0
- freetype=2.5.5=2
- funcsigs=1.0.2=py27hb9f6266_0
- functools32=3.2.3.2=py27_0
- get_terminal_size=1.0.0=py27_0
- gettext=0.19.8=1
- git=2.11.1=0
- html5lib=0.9999999=py27_0
- icu=54.1=0
- intel-openmp=2018.0.0=h8158457_8
- ipykernel=4.6.1=py27_0
- ipython=5.3.0=py27_0
- ipython-notebook=4.0.4=py27_0
- ipython_genutils=0.2.0=py27_0
- jbig=2.1=0
- jinja2=2.9.6=py27_0
- jpeg=9b=0
- jsonschema=2.6.0=py27_0
- jupyter_client=5.1.0=py27_0
- jupyter_core=4.3.0=py27_0
- krb5=1.13.2=0
- libcxx=4.0.1=h579ed51_0
- libcxxabi=4.0.1=hebd6815_0
- libffi=3.2.1=1
- libgfortran=3.0.1=h93005f0_2
- libiconv=1.14=0
- libpng=1.6.30=1
- libssh2=1.8.0=0
- libtiff=4.0.6=3
- llvmlite=0.21.0=py27hac8ee23_0
- markupsafe=1.0=py27_0
- matplotlib=2.0.2=np113py27_0
- mistune=0.7.4=py27_0
- mkl=2018.0.1=hfbd8650_4
- nbconvert=5.2.1=py27_0
- nbformat=4.4.0=py27_0
- notebook=5.0.0=py27_0
- numba=0.36.2=np113py27h7c931aa_0
- numpy=1.13.3=py27h62f9060_0
- pandas=0.20.3=py27_0
- pandocfilters=1.4.2=py27_0
- path.py=10.3.1=py27_0
- pathlib2=2.3.0=py27_0
- patsy=0.4.1=py27_0
- pcre=8.39=1
- pexpect=4.2.1=py27_0
- pickleshare=0.7.4=py27_0
- pip=9.0.1=py27_1
- prompt_toolkit=1.0.15=py27_0
- ptyprocess=0.5.2=py27_0
- pygments=2.2.0=py27_0
- pyparsing=2.2.0=py27_0
- pyqt=5.6.0=py27_2
- python=2.7.13=0
- python-dateutil=2.6.1=py27_0
- pytz=2017.2=py27_0
- pyyaml=3.12=py27_0
- pyzmq=16.0.2=py27_0
- qt=5.6.2=2
- readline=6.2=2
- scandir=1.5=py27_0
- scipy=1.0.0=py27h793f721_0
- seaborn=0.8=py27_0
- setuptools=36.4.0=py27_0
- simplegeneric=0.8.1=py27_1
- singledispatch=3.4.0.3=py27_0
- sip=4.18=py27_0
- six=1.10.0=py27_0
- sqlite=3.13.0=0
- ssl_match_hostname=3.5.0.1=py27_0
- statsmodels=0.8.0=np113py27_0
- subprocess32=3.2.7=py27_0
- terminado=0.6=py27_0
- testpath=0.3.1=py27_0
- tk=8.5.18=0
- tornado=4.5.2=py27_0
- traitlets=4.3.2=py27_0
- wcwidth=0.1.7=py27_0
- wheel=0.29.0=py27_0
- xz=5.2.3=0
- yaml=0.1.6=0
- zlib=1.2.11=0
- adwaita-icon-theme=3.24.0=1
- arrow=0.10.0=py27_0
- at-spi2-atk=2.24.1=2
- at-spi2-core=2.24.1=2
- atk=2.24.0=3
- cairo-gobject=1.14.8=8
- dbus-client=1.10.18=0
- dfu-programmer=0.7.2=2
- dstat-interface=1.4.6=py27_0
- dstat-interface-deps=1.0=0
- gdk-pixbuf=2.36.6=2
- glib=2.52.2=5
- gobject-introspection=1.52.1=2
- gtk3=3.22.15=4
- harfbuzz=1.4.6=3
- libepoxy=1.4.2=5
- libusb=1.0.21=0
- pango=1.40.6=2
- pixman=0.34.0=1
- py2cairo=1.10.0=py27_0
- pygobject3=3.24.2=py27_3
- pyserial=3.3=py27_0
- zmq-plugin=0.2.post14=py27_0
- pip:
- backports.shutil-get-terminal-size==1.0.0
- backports.shutil-which==3.5.1
- backports.ssl-match-hostname==3.5.0.1
- chardet==3.0.4
- colorama==0.3.9
- idna==2.6
- paver==1.2.4
- pygobject==3.24.1
- requests==2.18.4
- urllib3==1.22
- vmprof==0.4.10
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXFileReference section */
5F05410F1994220800185C41 /* build_windows.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = build_windows.py; sourceTree = "<group>"; };
5F87883C19072E86007B53E0 /* mpltest.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = mpltest.py; sourceTree = "<group>"; };
5FB0B8E1198ACD4B00FA6CB7 /* microdrop.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = microdrop.py; sourceTree = "<group>"; };
5FCB541B190591CD00CEB148 /* interface */ = {isa = PBXFileReference; lastKnownFileType = folder; path = interface; sourceTree = "<group>"; };
5FCB541D1905923800CEB148 /* interface_test.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = interface_test.py; sourceTree = "<group>"; };
5FCB54231905B6EE00CEB148 /* dstat_comm.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = dstat_comm.py; sourceTree = "<group>"; };
5FDC0DFD18FDAD79003F857A /* mpl.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = mpl.py; sourceTree = "<group>"; };
5FDC1E4218FF9572007AD04D /* glade1.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = glade1.py; sourceTree = "<group>"; };
5FF00FDC1942BD16004D38A8 /* setup.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = setup.py; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
5FDC0DF218FDACDA003F857A = {
isa = PBXGroup;
children = (
5FF00FDC1942BD16004D38A8 /* setup.py */,
5F87883C19072E86007B53E0 /* mpltest.py */,
5F05410F1994220800185C41 /* build_windows.py */,
5FCB541D1905923800CEB148 /* interface_test.py */,
5FB0B8E1198ACD4B00FA6CB7 /* microdrop.py */,
5FCB54231905B6EE00CEB148 /* dstat_comm.py */,
5FCB541B190591CD00CEB148 /* interface */,
5FDC0DFD18FDAD79003F857A /* mpl.py */,
5FDC1E4218FF9572007AD04D /* glade1.py */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
5FDC0DF718FDACDA003F857A /* dstatInterface */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "setup.py build_ext --inplace";
buildConfigurationList = 5FDC0DFA18FDACDA003F857A /* Build configuration list for PBXLegacyTarget "dstatInterface" */;
buildPhases = (
);
buildToolPath = /usr/local/bin/python;
buildWorkingDirectory = "/Users/mdryden/src/dstat-interface2/dstatInterface";
dependencies = (
);
name = dstatInterface;
passBuildSettingsInEnvironment = 1;
productName = dstatInterface;
};
/* End PBXLegacyTarget section */
/* Begin PBXProject section */
5FDC0DF318FDACDA003F857A /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0510;
ORGANIZATIONNAME = "Wheeler Lab";
};
buildConfigurationList = 5FDC0DF618FDACDA003F857A /* Build configuration list for PBXProject "dstatInterface" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 5FDC0DF218FDACDA003F857A;
projectDirPath = "";
projectRoot = "";
targets = (
5FDC0DF718FDACDA003F857A /* dstatInterface */,
);
};
/* End PBXProject section */
/* Begin XCBuildConfiguration section */
5FDC0DF818FDACDA003F857A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
5FDC0DF918FDACDA003F857A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
SDKROOT = macosx;
};
name = Release;
};
5FDC0DFB18FDACDA003F857A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEBUGGING_SYMBOLS = YES;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
5FDC0DFC18FDACDA003F857A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
5FDC0DF618FDACDA003F857A /* Build configuration list for PBXProject "dstatInterface" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5FDC0DF818FDACDA003F857A /* Debug */,
5FDC0DF918FDACDA003F857A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5FDC0DFA18FDACDA003F857A /* Build configuration list for PBXLegacyTarget "dstatInterface" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5FDC0DFB18FDACDA003F857A /* Debug */,
5FDC0DFC18FDACDA003F857A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 5FDC0DF318FDACDA003F857A /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:dstatInterface.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>0F1B5EE2-7AAC-4E6A-B3E7-4BA34F7B9450</string>
<key>IDESourceControlProjectName</key>
<string>dstatInterface</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>4464FFD1-344B-433F-9C56-38CC9C58511E</key>
<string>ssh://bitbucket.org/mkdryden/dstat-interface-2.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>dstatInterface/dstatInterface.xcodeproj/project.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>4464FFD1-344B-433F-9C56-38CC9C58511E</key>
<string>../../..</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>ssh://bitbucket.org/mkdryden/dstat-interface-2.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>110</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>4464FFD1-344B-433F-9C56-38CC9C58511E</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>4464FFD1-344B-433F-9C56-38CC9C58511E</string>
<key>IDESourceControlWCCName</key>
<string>dstat-interface2</string>
</dict>
</array>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5FDC0DF718FDACDA003F857A"
BuildableName = "dstatInterface"
BlueprintName = "dstatInterface"
ReferencedContainer = "container:dstatInterface.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "/Users/mdryden/src/dstat-interface2/dstatInterface"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<PathRunnable
FilePath = "/usr/local/Cellar/python/2.7.8/Frameworks/Python.framework/Versions/2.7/bin/python2.7">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5FDC0DF718FDACDA003F857A"
BuildableName = "dstatInterface"
BlueprintName = "dstatInterface"
ReferencedContainer = "container:dstatInterface.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
<CommandLineArgument
argument = "interface_test.py"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>dstatInterface.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>5FDC0DF718FDACDA003F857A</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
#!/usr/bin/env python
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import serial
from serial.tools import list_ports
import time
import struct
import multiprocessing as mp
def call_it(instance, name, args=(), kwargs=None):
"""Indirect caller for instance methods and multiprocessing.
Arguments:
instance -- instance to which the method belongs
name -- method to call
args -- passed to method
kwargs -- passed to method
"""
if kwargs is None:
kwargs = {}
return getattr(instance, name)(*args, **kwargs)
class delayedSerial(serial.Serial):
"""Extends Serial.write so that characters are output individually
with a slight delay
"""
def write(self, data):
for i in data:
serial.Serial.write(self, i)
time.sleep(.001)
class SerialDevices(object):
"""Retrieves and stores list of serial devices in self.ports"""
def __init__(self):
try:
self.ports, _, _ = zip(*list_ports.comports())
except ValueError:
self.ports = []
print "No serial ports found"
def refresh(self):
"""Refreshes list of ports."""
self.ports, _, _ = zip(*list_ports.comports())
class Experiment(object):
"""Store and acquire a potentiostat experiment. Meant to be subclassed
to by different experiment types and not used instanced directly.
"""
def run_wrapper(self, *argv):
"""Execute experiment indirectly using call_it to bypass lack of fork()
on Windows for multiprocessing.
"""
self.proc = mp.Process(target=call_it, args=(self, 'run', argv))
self.proc.start()
def __init__(self, parameters, main_pipe):
"""Adds commands for gain and ADC."""
self.parameters = parameters
self.main_pipe = main_pipe
self.databytes = 8
self.data_extra = [] # must be defined even when not needed
self.__gaintable = [1e2, 3e2, 3e3, 3e4, 3e5, 3e6, 3e7, 5e8]
self.gain = self.__gaintable[int(self.parameters['gain'])]
self.commands = ["A", "G"]
self.commands[0] += (self.parameters['adc_buffer'])
self.commands[0] += " "
self.commands[0] += (self.parameters['adc_rate'])
self.commands[0] += " "
self.commands[0] += (self.parameters['adc_pga'])
self.commands[0] += " "
self.commands[1] += (self.parameters['gain'])
self.commands[1] += " "
def run(self, ser_port):
"""Execute experiment. Connects and sends handshake signal to DStat
then sendsself.commands. Don't call directly as a process in Windows,
use run_wrapper instead.
Arguments:
ser_port -- address of serial port to use
"""
self.serial = delayedSerial(ser_port, 1024000, timeout=1)
self.serial.write("ck")
self.serial.flushInput()
for i in self.commands:
print i
self.serial.write('!')
while not self.serial.read().startswith("C"):
pass
self.serial.write(i)
if not self.serial_handler():
break
self.data_postprocessing()
self.serial.close()
self.main_pipe.close()
def serial_handler(self):
"""Handles incoming serial transmissions from DStat. Returns False
if stop button pressed and sends abort signal to instrument. Sends
data to self.main_pipe as result of self.data_handler).
"""
scan = 0
while True:
if self.main_pipe.poll():
print "abort"
if self.main_pipe.recv() == 'a':
self.serial.write('a')
return False
for line in self.serial:
if line.startswith('B'):
self.main_pipe.send(self.data_handler(
(scan, self.serial.read(size=self.databytes))))
elif line.startswith('S'):
scan += 1
elif line.startswith("#"):
print line
elif line.lstrip().startswith("no"):
print line
self.serial.flushInput()
return True
def data_handler(self, data_input):
"""Takes data_input as tuple -- (scan, data).
Returns:
(scan number, [voltage, current]) -- voltage in mV, current in A
"""
scan, data = data_input
voltage, current = struct.unpack('<Hl', data) #uint16 + int32
return (scan,
[(voltage-32768)*3000./65536, current*(1.5/self.gain/8388607)])
def data_postprocessing(self):
"""No data postprocessing done by default, can be overridden
in subclass.
"""
pass
class Chronoamp(Experiment):
"""Chronoamperometry experiment"""
def __init__(self, parameters, main_pipe):
super(Chronoamp, self).__init__(parameters, main_pipe)
self.datatype = "linearData"
self.xlabel = "Time (s)"
self.ylabel = "Current (A)"
self.data = [[], []]
self.datalength = 2
self.databytes = 8
self.xmin = 0
self.xmax = 0
for i in self.parameters['time']:
self.xmax += int(i)
self.commands += "R"
self.commands[2] += str(len(self.parameters['potential']))
self.commands[2] += " "
for i in self.parameters['potential']:
self.commands[2] += str(int(i*(65536./3000)+32768))
self.commands[2] += " "
for i in self.parameters['time']:
self.commands[2] += str(i)
self.commands[2] += " "
def data_handler(self, data_input):
"""Overrides Experiment method to not convert x axis to mV."""
scan, data = data_input
# 2*uint16 + int32
seconds, milliseconds, current = struct.unpack('<HHl', data)
return (scan,
[seconds+milliseconds/1000., current*(1.5/self.gain/8388607)])
class LSVExp(Experiment):
"""Linear Scan Voltammetry experiment"""
def __init__(self, parameters, main_pipe):
super(LSVExp, self).__init__(parameters, main_pipe)
self.datatype = "linearData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = [[], []]
self.datalength = 2
self.databytes = 6 # uint16 + int32
self.xmin = self.parameters['start']
self.xmax = self.parameters['stop']
self.init() # need to call after xmin and xmax are set
self.commands += "L"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['clean_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['dep_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(self.parameters['start'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['stop'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['slope'])
self.commands[2] += " "
class CVExp(Experiment):
"""Cyclic Voltammetry experiment"""
def __init__(self, parameters, main_pipe):
super(CVExp, self).__init__(parameters, main_pipe)
self.datatype = "CVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = [[], []]
self.datalength = 2 * self.parameters['scans'] # x and y for each scan
self.databytes = 6 # uint16 + int32
self.xmin = self.parameters['v1']
self.xmax = self.parameters['v2']
self.commands += "C"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['clean_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['dep_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(self.parameters['v1'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['v2'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['start'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['scans'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['slope'])
self.commands[2] += " "
class SWVExp(Experiment):
"""Square Wave Voltammetry experiment"""
def __init__(self, parameters, main_pipe):
super(SWVExp, self).__init__(parameters, main_pipe)
self.datatype = "SWVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = [[], []] # only difference stored here
self.datalength = 2 * self.parameters['scans']
self.databytes = 10
self.xmin = self.parameters['start']
self.xmax = self.parameters['stop']
# forward/reverse stored here - needs to be after
# self.init to keep from being redefined
self.data_extra = [[], []]
self.commands += "S"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['clean_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['dep_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(self.parameters['start'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['stop'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['step'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['pulse'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['freq'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['scans'])
self.commands[2] += " "
def data_handler(self, input_data):
"""Overrides Experiment method to calculate difference current"""
scan, data = input_data
# uint16 + int32
voltage, forward, reverse = struct.unpack('<Hll', data)
return (scan, [(voltage-32768)*3000./65536,
(forward-reverse)*(1.5/self.gain/8388607),
forward*(1.5/self.gain/8388607),
reverse*(1.5/self.gain/8388607)])
class DPVExp(SWVExp):
"""Diffential Pulse Voltammetry experiment."""
def __init__(self, parameters, main_pipe):
"""Overrides SWVExp method"""
super(DPVExp, self).__init__(parameters, main_pipe)
self.datatype = "SWVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = [[], []] # only difference stored here
self.datalength = 2
self.databytes = 10
self.xmin = self.parameters['start']
self.xmax = self.parameters['stop']
self.init()
# forward/reverse stored here - needs to be after self.init to
# keep from being redefined
self.data_extra = [[], []]
self.commands += "D"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['clean_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(int(self.parameters['dep_mV']*
(65536./3000)+32768))
self.commands[2] += " "
self.commands[2] += str(self.parameters['start'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['stop'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['step'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['pulse'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['period'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['width'])
self.commands[2] += " "
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gtk, io, os
import numpy as np
from datetime import datetime
def manSave(current_exp):
exp = current_exp
fcd = gtk.FileChooserDialog("Save...", None, gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK))
filters = [gtk.FileFilter()]
filters[0].set_name("NumPy binary (.npy)")
filters[0].add_pattern("*.npy")
filters.append(gtk.FileFilter())
filters[1].set_name("Space separated text (.txt)")
filters[1].add_pattern("*.txt")
fcd.set_do_overwrite_confirmation(True)
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == gtk.RESPONSE_OK:
path = fcd.get_filename()
print "Selected filepath: %s" % path
filter_selection = fcd.get_filter().get_name()
if filter_selection.endswith("(.npy)"):
npy(exp, path)
elif filter_selection.endswith("(.txt)"):
text(exp, path)
fcd.destroy()
elif response == gtk.RESPONSE_CANCEL:
fcd.destroy()
def plotSave(plot):
fcd = gtk.FileChooserDialog("Save Plot…", None,
gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK))
filters = [gtk.FileFilter()]
filters[0].set_name("Portable Document Format (.pdf)")
filters[0].add_pattern("*.pdf")
filters.append(gtk.FileFilter())
filters[1].set_name("Portable Network Graphics (.png)")
filters[1].add_pattern("*.png")
fcd.set_do_overwrite_confirmation(True)
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == gtk.RESPONSE_OK:
path = fcd.get_filename()
print "Selected filepath: %s" % path
filter_selection = fcd.get_filter().get_name()
if filter_selection.endswith("(.pdf)"):
if not path.endswith(".pdf"):
path += ".pdf"
elif filter_selection.endswith("(.png)"):
if not path.endswith(".png"):
path += ".png"
plot.figure.savefig(path) # determines format from file extension
fcd.destroy()
elif response == gtk.RESPONSE_CANCEL:
fcd.destroy()
def autoSave(current_exp, dir_button, name, expnumber):
if name == "":
name = "file"
path = dir_button.get_filename()
path += '/'
path += name
path += str(expnumber)
text(current_exp, path, auto=True)
def autoPlot(plot, dir_button, name, expnumber):
if name == "":
name = "file"
path = dir_button.get_filename()
path += '/'
path += name
path += str(expnumber)
if path.endswith(".pdf"):
path = path.rstrip(".pdf")
j = 1
while os.path.exists("".join([path, ".pdf"])):
if j > 1:
path = path[:-len(str(j))]
path += str(j)
j += 1
path += ".pdf"
plot.figure.savefig(path)
def npy(exp, path, auto=False):
if path.endswith(".npy"):
path = path.rstrip(".npy")
data = np.array(exp.data)
if auto == True:
j = 1
while os.path.exists("".join([path, ".npy"])):
if j > 1:
path = path[:-len(str(j))]
path += str(j)
j += 1
np.save(path, data)
def text(exp, path, auto=False):
if path.endswith(".txt"):
path = path.rstrip(".txt")
if auto == True:
j = 1
while os.path.exists("".join([path, ".txt"])):
if j > 1:
path = path[:-len(str(j))]
path += str(j)
j += 1
path += ".txt"
file = open(path, 'w')
time = datetime.now()
data = np.array(exp.data)
header = "".join(['#', time.isoformat(), "\n#"])
for i in exp.commands:
header += i
file.write("".join([header, '\n']))
for col in zip(*exp.data):
for row in col:
file.write(str(row)+ " ")
file.write('\n')
file.close()
# -*- mode: python -*-
a = Analysis(['interface_test.py'],
hiddenimports=[],
hookspath=None,
runtime_hooks=None)
glade_tree = Tree('./interface', prefix = 'interface', excludes=['*.py','*.pyc'])
drivers_tree = Tree('./drivers', prefix = 'drivers')
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='interface_test.exe',
debug=False,
strip=None,
upx=True,
console=True )
coll = COLLECT(exe,
drivers_tree,
glade_tree,
a.binaries,
a.zipfiles,
a.datas,
strip=None,
upx=True,
name='interface_test')
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" GUI Interface for Wheeler Lab DStat """
import sys
try:
import pygtk
pygtk.require('2.0')
except ImportError:
print('PyGTK 2.0 not available')
sys.exit(1)
try:
import gtk
except ImportError:
print('GTK not available')
sys.exit(1)
try:
import gobject
except ImportError:
print('gobject not available')
sys.exit(1)
import interface.save as save
import dstat_comm as comm
import interface.exp_window as exp_window
import interface.adc_pot as adc_pot
import plot
import microdrop
from serial import SerialException
import multiprocessing
import time
class Error(Exception):
"""Copies Exception class"""
pass
class InputError(Error):
"""Exception raised for errors in the input. Extends Error class.
Attributes:
expr -- input expression in which the error occurred
msg -- error message
"""
def __init__(self, expr, msg):
self.expr = expr
self.msg = msg
class Main(object):
"""Main program """
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file('interface/dstatinterface.glade')
self.builder.connect_signals(self)
self.cell = gtk.CellRendererText()
#create instance of interface components
self.statusbar = self.builder.get_object('statusbar')
self.window = self.builder.get_object('window1')
self.aboutdialog = self.builder.get_object('aboutdialog1')
self.rawbuffer = self.builder.get_object('databuffer1')
self.databuffer = self.builder.get_object('databuffer2')
self.stopbutton = self.builder.get_object('pot_stop')
self.startbutton = self.builder.get_object('pot_start')
self.adc_pot = adc_pot.adc_pot()
self.error_context_id = self.statusbar.get_context_id("error")
self.message_context_id = self.statusbar.get_context_id("message")
self.plotwindow = self.builder.get_object('plotbox')
self.exp_window = exp_window.Experiments(self.builder)
#setup autosave
self.autosave_checkbox = self.builder.get_object('autosave_checkbutton')
self.autosavedir_button = self.builder.get_object('autosavedir_button')
self.autosavename = self.builder.get_object('autosavename')
self.plot = plot.plotbox(self.plotwindow)
#fill adc_pot_box
self.adc_pot_box = self.builder.get_object('gain_adc_box')
self.adc_pot_container = self.adc_pot.builder.get_object('vbox1')
self.adc_pot_container.reparent(self.adc_pot_box)
#fill serial
self.serial_combobox = self.builder.get_object('serial_combobox')
self.serial_combobox.pack_start(self.cell, True)
self.serial_combobox.add_attribute(self.cell, 'text', 0)
self.serial_liststore = self.builder.get_object('serial_liststore')
self.serial_devices = comm.SerialDevices()
for i in self.serial_devices.ports:
self.serial_liststore.append([i])
self.serial_combobox.set_active(0)
#initialize experiment selection combobox
self.expcombobox = self.builder.get_object('expcombobox')
self.expcombobox.pack_start(self.cell, True)
self.expcombobox.add_attribute(self.cell, 'text', 2)
self.expcombobox.set_active(0)
self.spinner = self.builder.get_object('spinner')
self.mainwindow = self.builder.get_object('window1')
self.mainwindow.set_title("Dstat Interface 0.1")
self.mainwindow.show_all()
self.on_expcombobox_changed()
self.expnumber = 0
self.menu_dropbot_connect = self.builder.get_object(
'menu_dropbot_connect')
self.menu_dropbot_disconnect = self.builder.get_object(
'menu_dropbot_disconnect')
self.dropbot_enabled = False
self.dropbot_triggered = False
def on_window1_destroy(self, object, data=None):
""" Quit when main window closed."""
gtk.main_quit()
def on_gtk_quit_activate(self, menuitem, data=None):
"""Quit when Quit selected from menu."""
gtk.main_quit()
def on_gtk_about_activate(self, menuitem, data=None):
"""Display the about window."""
self.response = self.aboutdialog.run() # waits for user to click close
self.aboutdialog.hide()
def on_expcombobox_changed(self, data=None):
"""Change the experiment window when experiment box changed."""
model = self.expcombobox.get_model()
_, id, _ = model[self.expcombobox.get_active()] # id is in 2nd col
self.statusbar.remove_all(self.error_context_id)
if not self.exp_window.set_exp(id):
self.statusbar.push(
self.error_context_id, "Experiment not yet implemented")
def on_serial_refresh_clicked(self, data=None):
"""Refresh list of serial devices."""
self.serial_devices.refresh()
self.serial_liststore.clear()
for i in self.serial_devices.ports:
self.serial_liststore.append([i])
def on_pot_start_clicked(self, data=None):
"""Run currently visible experiment."""
def exceptions():
""" Cleans up after errors """
if self.dropbot_enabled == True:
if self.dropbot_triggered == True:
self.dropbot_triggered = False
print "finallydone"
self.microdrop.reply(microdrop.EXPFINISHED)
self.microdrop_proc = gobject.timeout_add(500,
self.microdrop_listen)
self.spinner.stop()
self.startbutton.set_sensitive(True)
self.stopbutton.set_sensitive(False)
selection = self.expcombobox.get_active()
parameters = {}
if self.adc_pot.buffer_toggle.get_active(): #True if box checked
parameters['adc_buffer'] = "2"
else:
parameters['adc_buffer'] = "0"
srate_model = self.adc_pot.srate_combobox.get_model()
pga_model = self.adc_pot.pga_combobox.get_model()
gain_model = self.adc_pot.gain_combobox.get_model()
parameters['adc_rate'] = srate_model.get_value(
self.adc_pot.srate_combobox.get_active_iter(), 2) # third column
parameters['adc_pga'] = pga_model.get_value(
self.adc_pot.pga_combobox.get_active_iter(), 2)
parameters['gain'] = gain_model.get_value(
self.adc_pot.gain_combobox.get_active_iter(), 2)
self.line = 0
self.lastline = 0
self.lastdataline = 0
self.spinner.start()
self.startbutton.set_sensitive(False)
self.stopbutton.set_sensitive(True)
self.statusbar.remove_all(self.error_context_id)
try:
if selection == 0: # CA
# Add experiment parameters to existing
parameters.update(self.exp_window.get_params('cae'))
if not parameters['potential']:
raise InputError(parameters['potential'],
"Step table is empty")
self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True)
self.current_exp = comm.Chronoamp(parameters, self.send_p)
self.plot.clearall()
self.plot.changetype(self.current_exp)
self.rawbuffer.set_text("")
self.rawbuffer.place_cursor(self.rawbuffer.get_start_iter())
for i in self.current_exp.commands:
self.rawbuffer.insert_at_cursor(i)
self.current_exp.run_wrapper(self.serial_liststore.get_value(
self.serial_combobox.get_active_iter(), 0))
self.send_p.close() # need for EOF signal to work
self.plot_proc = gobject.timeout_add(200,
self.experiment_running_plot)
gobject.idle_add(self.experiment_running)
return
elif selection == 1: # LSV
parameters.update(self.exp_window.get_params('lsv'))
#check parameters are within hardware limits
if (parameters['clean_mV'] > 1499 or
parameters['clean_mV'] < -1500):
raise InputError(parameters['clean_mV'],
"Clean potential exceeds hardware limits.")
if (parameters['dep_mV'] > 1499 or
parameters['dep_mV'] < -1500):
raise InputError(parameters['dep_mV'],
"Deposition potential exceeds hardware limits.")
if (parameters['clean_s'] < 0):
raise InputError(parameters['clean_s'],
"Clean time cannot be negative.")
if (parameters['dep_s'] < 0):
raise InputError(parameters['dep_s'],
"Deposition time cannot be negative.")
if (parameters['start'] > 1499 or parameters['start'] < -1500):
raise InputError(parameters['start'],
"Start parameter exceeds hardware limits.")
if (parameters['stop'] > 1499 or parameters['stop'] < -1500):
raise InputError(parameters['stop'],
"Stop parameter exceeds hardware limits.")
if (parameters['slope'] > 2000 or parameters['slope'] < 1):
raise InputError(parameters['slope'],
"Slope parameter exceeds hardware limits.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True)
self.current_exp = comm.LSVExp(parameters, self.send_p)
self.plot.clearall()
self.plot.changetype(self.current_exp)
self.current_exp.run_wrapper(
self.serial_liststore.get_value(
self.serial_combobox.get_active_iter(), 0))
self.send_p.close()
self.plot_proc = gobject.timeout_add(200,
self.experiment_running_plot)
gobject.idle_add(self.experiment_running)
return
elif selection == 2: # CV
parameters.update(self.exp_window.get_params('cve'))
# check parameters are within hardware limits
if (parameters['clean_mV'] > 1499 or
parameters['clean_mV'] < -1500):
raise InputError(parameters['clean_mV'],
"Clean potential exceeds hardware limits.")
if (parameters['dep_mV'] > 1499 or
parameters['dep_mV'] < -1500):
raise InputError(parameters['dep_mV'],
"Deposition potential exceeds hardware limits.")
if (parameters['clean_s'] < 0):
raise InputError(parameters['clean_s'],
"Clean time cannot be negative.")
if (parameters['dep_s'] < 0):
raise InputError(parameters['dep_s'],
"Deposition time cannot be negative.")
if (parameters['start'] > 1499 or parameters['start'] < -1500):
raise InputError(parameters['start'],
"Start parameter exceeds hardware limits.")
if (parameters['slope'] > 2000 or parameters['slope'] < 1):
raise InputError(parameters['slope'],
"Slope parameter exceeds hardware limits.")
if (parameters['v1'] > 1499 or parameters['v1'] < -1500):
raise InputError(parameters['v1'],
"Vertex 1 parameter exceeds hardware limits.")
if (parameters['v2'] > 1499 or parameters['v2'] < -1500):
raise InputError(parameters['v2'],
"Vertex 2 parameter exceeds hardware limits.")
if (parameters['scans'] < 1 or parameters['scans'] > 255):
raise InputError(parameters['scans'],
"Scans parameter outside limits.")
if parameters['v1'] == parameters['v2']:
raise InputError(parameters['v1'],
"Vertex 1 cannot equal Vertex 2.")
self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True)
self.current_exp = comm.CVExp(parameters, self.send_p)
self.plot.clearall()
self.plot.changetype(self.current_exp)
self.current_exp.run_wrapper(
self.serial_liststore.get_value(
self.serial_combobox.get_active_iter(), 0))
self.send_p.close()
self.plot_proc = gobject.timeout_add(200,
self.experiment_running_plot)
gobject.idle_add(self.experiment_running)
return
elif selection == 3: # SWV
parameters.update(self.exp_window.get_params('swv'))
if parameters['cyclic_checkbutton'] :
if parameters['scans'] < 1:
raise InputError(parameters['scans'],
"Must have at least one scan.")
else:
parameters['scans'] = 0
# check parameters are within hardware limits (doesn't
# check if pulse will go out of bounds, but instrument
# checks this (I think))
if (parameters['clean_mV'] > 1499 or
parameters['clean_mV'] < -1500):
raise InputError(parameters['clean_mV'],
"Clean potential exceeds hardware limits.")
if (parameters['dep_mV'] > 1499 or
parameters['dep_mV'] < -1500):
raise InputError(parameters['dep_mV'],
"Deposition potential exceeds hardware limits.")
if (parameters['clean_s'] < 0):
raise InputError(parameters['clean_s'],
"Clean time cannot be negative.")
if (parameters['dep_s'] < 0):
raise InputError(parameters['dep_s'],
"Deposition time cannot be negative.")
if (parameters['start'] > 1499 or parameters['start'] < -1500):
raise InputError(parameters['start'],
"Start parameter exceeds hardware limits.")
if (parameters['step'] > 200 or parameters['step'] < 1):
raise InputError(parameters['step'],
"Step height parameter exceeds hardware limits.")
if (parameters['stop'] > 1499 or parameters['stop'] < -1500):
raise InputError(parameters['stop'],
"Stop parameter exceeds hardware limits.")
if (parameters['pulse'] > 150 or parameters['pulse'] < 1):
raise InputError(parameters['pulse'],
"Pulse height parameter exceeds hardware limits.")
if (parameters['freq'] < 1 or parameters['freq'] > 1000):
raise InputError(parameters['freq'],
"Frequency parameter outside limits.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True)
self.current_exp = comm.SWVExp(parameters, self.send_p)
self.plot.clearall()
self.plot.changetype(self.current_exp)
self.current_exp.run_wrapper(
self.serial_liststore.get_value(
self.serial_combobox.get_active_iter(), 0))
self.send_p.close()
self.plot_proc = gobject.timeout_add(200,
self.experiment_running_plot)
gobject.idle_add(self.experiment_running)
return
elif selection == 4: # DPV
parameters.update(self.exp_window.get_params('dpv'))
if (parameters['clean_mV'] > 1499 or
parameters['clean_mV'] < -1500):
raise InputError(parameters['clean_mV'],
"Clean potential exceeds hardware limits.")
if (parameters['dep_mV'] > 1499 or
parameters['dep_mV'] < -1500):
raise InputError(parameters['dep_mV'],
"Deposition potential exceeds hardware limits.")
if (parameters['clean_s'] < 0):
raise InputError(parameters['clean_s'],
"Clean time cannot be negative.")
if (parameters['dep_s'] < 0):
raise InputError(parameters['dep_s'],
"Deposition time cannot be negative.")
if (parameters['start'] > 1499 or parameters['start'] < -1500):
raise InputError(parameters['start'],
"Start parameter exceeds hardware limits.")
if (parameters['step'] > 200 or parameters['step'] < 1):
raise InputError(parameters['step'],
"Step height parameter exceeds hardware limits.")
if (parameters['stop'] > 1499 or parameters['stop'] < -1500):
raise InputError(parameters['stop'],
"Stop parameter exceeds hardware limits.")
if (parameters['pulse'] > 150 or parameters['pulse'] < 1):
raise InputError(parameters['pulse'],
"Pulse height parameter exceeds hardware limits.")
if (parameters['period'] < 1 or parameters['period'] > 1000):
raise InputError(parameters['period'],
"Period parameter outside limits.")
if (parameters['width'] < 1 or parameters['width'] > 1000):
raise InputError(parameters['width'],
"Width parameter outside limits.")
if parameters['period'] <= parameters['width']:
raise InputError(parameters['width'],
"Width must be less than period.")
if parameters['start'] == parameters['stop']:
raise InputError(parameters['start'],
"Start cannot equal Stop.")
self.recv_p, self.send_p = multiprocessing.Pipe(duplex=True)
self.current_exp = comm.DPVExp(parameters, self.send_p)
self.plot.clearall()
self.plot.changetype(self.current_exp)
self.current_exp.run_wrapper(
self.serial_liststore.get_value(
self.serial_combobox.get_active_iter(), 0))
self.send_p.close()
self.plot_proc = gobject.timeout_add(200,
self.experiment_running_plot)
gobject.idle_add(self.experiment_running)
return
else:
self.statusbar.push(self.error_context_id,
"Experiment not yet implemented.")
exceptions()
except ValueError:
self.statusbar.push(self.error_context_id,
"Experiment parameters must be integers.")
exceptions()
except InputError as err:
self.statusbar.push(self.error_context_id, err.msg)
exceptions()
except SerialException:
self.statusbar.push(self.error_context_id,
"Could not establish serial connection.")
exceptions()
except AssertionError as err:
self.statusbar.push(self.error_context_id, str(err))
exceptions()
def experiment_running(self):
"""Receive data from experiment process and add to current_exp.data.
Run in GTK main loop.
Returns:
True -- when experiment is continuing to keep function in GTK's queue.
False -- when experiment process signals EOFError or IOError to remove
function from GTK's queue.
"""
try:
if self.recv_p.poll():
self.line, data = self.recv_p.recv()
if self.line > self.lastdataline:
self.current_exp.data += [[], []]
if len(data) > 2:
self.current_exp.data_extra += [[], []]
self.lastdataline = self.line
for i in range(2):
self.current_exp.data[2*self.line+i].append(data[i])
if len(data) > 2:
self.current_exp.data_extra[2*self.line+i].append(
data[i+2])
else:
time.sleep(.001)
return True
except EOFError:
self.experiment_done()
return False
except IOError:
self.experiment_done()
return False
def experiment_running_plot(self):
"""Plot all data in current_exp.data.
Run in GTK main loop. Always returns True so must be manually
removed from GTK's queue.
"""
if self.line > self.lastline:
self.plot.addline()
# make sure all of last line is added
self.plot.updateline(self.current_exp, self.lastline)
self.lastline = self.line
self.plot.updateline(self.current_exp, self.line)
self.plot.redraw()
return True
def experiment_done(self):
"""Clean up after data acquisition is complete. Update plot and
copy data to raw data tab. Saves data if autosave enabled.
"""
gobject.source_remove(self.plot_proc) # stop automatic plot update
self.experiment_running_plot() # make sure all data updated on plot
self.databuffer.set_text("")
self.databuffer.place_cursor(self.databuffer.get_start_iter())
self.rawbuffer.insert_at_cursor("\n")
self.rawbuffer.set_text("")
self.rawbuffer.place_cursor(self.rawbuffer.get_start_iter())
for i in self.current_exp.commands:
self.rawbuffer.insert_at_cursor(i)
for col in zip(*self.current_exp.data):
for row in col:
self.rawbuffer.insert_at_cursor(str(row)+ " ")
self.rawbuffer.insert_at_cursor("\n")
if self.current_exp.data_extra:
for col in zip(*self.current_exp.data_extra):
for row in col:
self.databuffer.insert_at_cursor(str(row)+ " ")
self.databuffer.insert_at_cursor("\n")
if self.autosave_checkbox.get_active():
save.autoSave(self.current_exp, self.autosavedir_button,
self.autosavename.get_text(), self.expnumber)
save.autoPlot(self.plot, self.autosavedir_button,
self.autosavename.get_text(), self.expnumber)
self.expnumber += 1
if self.dropbot_enabled == True:
if self.dropbot_triggered == True:
self.dropbot_triggered = False
print "expdone"
self.microdrop.reply(microdrop.EXPFINISHED)
self.microdrop_proc = gobject.timeout_add(500,
self.microdrop_listen)
self.spinner.stop()
self.startbutton.set_sensitive(True)
self.stopbutton.set_sensitive(False)
def on_pot_stop_clicked(self, data=None):
"""Stop current experiment. Signals experiment process to stop."""
if self.recv_p:
print "stop"
self.recv_p.send('a')
def on_file_save_exp_activate(self, menuitem, data=None):
"""Activate dialogue to save current experiment data. """
if self.current_exp:
save.manSave(self.current_exp)
def on_file_save_plot_activate(self, menuitem, data=None):
"""Activate dialogue to save current plot."""
save.plotSave(self.plot)
def on_menu_dropbot_connect_activate(self, menuitem, data=None):
"""Listen for remote control connection from µDrop."""
self.microdrop = microdrop.microdropConnection()
self.dropbot_enabled = True
self.menu_dropbot_connect.set_sensitive(False)
self.menu_dropbot_disconnect.set_sensitive(True)
self.statusbar.push(self.message_context_id,
"Waiting for µDrop to connect…")
self.microdrop_proc = gobject.timeout_add(500, self.microdrop_listen)
def on_menu_dropbot_disconnect_activate(self, menuitem, data= None):
"""Disconnect µDrop connection and stop listening."""
gobject.source_remove(self.microdrop_proc)
self.microdrop.reset()
del self.microdrop
self.dropbot_enabled = False
self.menu_dropbot_connect.set_sensitive(True)
self.menu_dropbot_disconnect.set_sensitive(False)
def microdrop_listen(self):
"""Manage signals from µDrop. Must be added to GTK's main loop to
run periodically.
"""
drdy, data = self.microdrop.listen()
if drdy == False:
return True
if data == microdrop.EXP_FINISH_REQ:
if self.dropbot_triggered:
self.on_pot_start_clicked()
return False # Removes function from GTK's main loop
else:
print "WAR: µDrop requested experiment finish confirmation \
without starting experiment."
self.microdrop.reply(microdrop.EXPFINISHED)
elif data == microdrop.STARTEXP:
self.microdrop.connected = True
self.statusbar.push(self.message_context_id, "µDrop connected.")
self.dropbot_triggered = True
self.microdrop.reply(microdrop.START_REP)
else:
print "WAR: Received invalid command from µDrop"
self.microdrop.reply(microdrop.INVAL_CMD)
return True
if __name__ == "__main__":
multiprocessing.freeze_support()
gobject.threads_init()
MAIN = Main()
gtk.main()
\ No newline at end of file
#!/usr/bin/env python
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Creates data plot.
"""
import gtk
from matplotlib.figure import Figure
#from matplotlib.backends.backend_gtkcairo\
# import FigureCanvasGTKCairo as FigureCanvas
#from matplotlib.backends.backend_gtkcairo\
# import NavigationToolbar2Cairo as NavigationToolbar
from matplotlib.backends.backend_gtkagg \
import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.backends.backend_gtkagg \
import NavigationToolbar2GTKAgg as NavigationToolbar
class plotbox(object):
"""Contains main data plot and associated methods."""
def __init__(self, plotwindow_instance):
"""Creates plot and moves it to a gtk container.
Arguments:
plotwindow_instance -- gtk container to hold plot.
"""
self.figure = Figure()
self.figure.subplots_adjust(left=0.07, bottom=0.07,
right=0.96, top=0.96)
self.axe1 = self.figure.add_subplot(111)
self.lines = self.axe1.plot([0, 1], [0, 1])
self.axe1.ticklabel_format(style='sci', scilimits=(0, 3),
useOffset=False, axis='y')
self.canvas = FigureCanvas(self.figure)
self.win = gtk.Window()
self.vbox = gtk.VBox()
self.win.add(self.vbox)
self.vbox.pack_start(self.canvas)
self.toolbar = NavigationToolbar(self.canvas, self.win)
self.vbox.pack_start(self.toolbar, False, False)
self.vbox.reparent(plotwindow_instance)
def clearall(self):
"""Remove all lines on plot. """
for i in self.lines:
i.remove()
self.lines = self.axe1.plot([0, 1], [0, 1])
def clearline(self, line_number):
"""Remove a line specified by line_number."""
self.lines[line_number].remove()
self.lines.pop(line_number)
def addline(self):
"""Add a new line to plot. (initialized with dummy data)))"""
self.lines.append(self.axe1.plot([0, 1], [0, 1])[0])
def updateline(self, Experiment, line_number):
"""Update a line specified by line_number with data stored in
the Experiment instance.
"""
# limits display to 2000 data points per line
divisor = len(Experiment.data[1+line_number*2]) // 2000 + 1
self.lines[line_number].set_ydata(
Experiment.data[1+line_number*2][1::divisor])
self.lines[line_number].set_xdata(
Experiment.data[line_number*2][1::divisor])
def changetype(self, Experiment):
"""Change plot type. Set axis labels and x bounds to those stored
in the Experiment instance.
"""
self.axe1.set_xlabel(Experiment.xlabel)
self.axe1.set_ylabel(Experiment.ylabel)
self.axe1.set_xlim(Experiment.xmin, Experiment.xmax)
self.figure.canvas.draw()
def redraw(self):
"""Autoscale and refresh the plot."""
self.axe1.relim()
self.axe1.autoscale(True, axis = 'y')
self.figure.canvas.draw()
return True
#!/usr/bin/env python
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Functions for analyzing data.
"""
import logging
import os
from numpy import mean, trapz
logger = logging.getLogger(__name__)
mod_dir = os.path.dirname(os.path.abspath(__file__))
class AnalysisOptions(object):
"""Analysis options window."""
def __init__(self, builder):
self.builder = builder
self.builder.add_from_file(
os.path.join(mod_dir, 'interface/analysis_options.glade'))
self.builder.connect_signals(self)
self.window = self.builder.get_object('analysis_dialog')
self.stats_button = self.builder.get_object('stats_button')
self.stats_start = self.builder.get_object('stats_start_spin')
self.stats_start_button = self.builder.get_object('stats_start_button')
self.stats_stop = self.builder.get_object('stats_stop_spin')
self.stats_stop_button = self.builder.get_object('stats_stop_button')
self.stats_button.connect('toggled',
self.on_button_toggled_hide,
[self.stats_stop,
self.stats_stop_button,
self.stats_start,
self.stats_start_button
]
)
self._params = {'stats_true':False,
'stats_start_true':False,
'stats_stop':0,
'stats_stop_true':False,
'stats_start':0
}
def show(self):
"""Show options window."""
self.window.run()
self.window.hide()
def on_button_toggled_hide(self, control, widgets):
"""Hide unchecked fields"""
active = control.get_active()
for widget in widgets:
widget.set_sensitive(active)
@property
def params(self):
"""Getter for analysis params"""
self._params['stats_true'] = self.stats_button.get_active()
self._params['stats_start_true'] = self.stats_start_button.get_active()
self._params['stats_start'] = self.stats_start.get_value()
self._params['stats_stop_true'] = self.stats_stop_button.get_active()
self._params['stats_stop'] = self.stats_stop.get_value()
return self._params
@params.setter
def params(self, params):
for key in self._params:
if key in params:
self._params[key] = params[key]
self.stats_button.set_active(self._params['stats_true'])
self.stats_start_button.set_active(self._params['stats_start_true'])
self.stats_start.set_value(self._params['stats_start'])
self.stats_stop_button.set_active(self._params['stats_stop_true'])
self.stats_stop.set_value(self._params['stats_stop'])
def do_analysis(experiment):
"""Takes an experiment class instance and runs selected analysis."""
experiment.analysis = {}
if experiment.parameters['stats_true']:
if (experiment.parameters['stats_start_true'] or
experiment.parameters['stats_stop_true']):
if experiment.parameters['stats_start_true']:
start = experiment.parameters['stats_start']
else:
start = min(experiment.data['data'][0][0])
if experiment.parameters['stats_stop_true']:
stop = experiment.parameters['stats_stop']
else:
stop = min(experiment.data['data'][0][0])
data = _data_slice(experiment.data['data'],
start,
stop
)
else:
data = experiment.data['data']
experiment.analysis.update(_summary_stats(data))
try:
x, y = experiment.data['ft'][0]
experiment.analysis['FT Integral'] = _integrateSpectrum(
x,
y,
float(experiment.parameters['sync_freq']),
float(experiment.parameters['fft_int'])
)
except KeyError:
pass
def _data_slice(data, start, stop):
"""Accepts data (as list of tuples of lists) and returns copy of data
between start and stop (in whatever x-axis units for the experiment type).
"""
output = []
for scan in range(len(data)):
t = []
for i in range(len(data[scan])):
t.append([])
output.append(tuple(t))
for i in range(len(data[scan][0])): # x-axis column
if data[scan][0][i] >= start or data[scan][0][i] <= stop:
for d in range(len(output[scan])):
output[scan][d].append(data[scan][d][i])
return output
def _summary_stats(data):
"""Takes data and returns summary statistics of first y variable as dict of
name, (scan, values).
"""
stats = {'min':[],'max':[], 'mean':[]}
for scan in range(len(data)):
stats['min'].append(
(scan, min(data[scan][1]))
)
stats['max'].append(
(scan, max(data[scan][1]))
)
stats['mean'].append(
(scan, mean(data[scan][1]))
)
return stats
def _integrateSpectrum(x, y, target, bandwidth):
"""
Returns integral of range of bandwidth centered on target.
"""
j = 0
k = len(x)
for i in range(len(x)):
if x[i] >= target-bandwidth/2:
j = i
break
for i in range(j,len(x)):
if x[i] >= target+bandwidth/2:
k = i
break
return [(0, trapz(y=y[j:k], x=x[j:k]))]
\ No newline at end of file