From 1d29b19324d73b394d096d277e8b9e4fa840e5a6 Mon Sep 17 00:00:00 2001 From: "William H.P. Nielsen" Date: Wed, 13 Sep 2017 09:41:33 +0200 Subject: [PATCH 01/18] add ZNB20.py file referencing ZNB driver (#734) --- qcodes/instrument_drivers/rohde_schwarz/ZNB20.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 qcodes/instrument_drivers/rohde_schwarz/ZNB20.py diff --git a/qcodes/instrument_drivers/rohde_schwarz/ZNB20.py b/qcodes/instrument_drivers/rohde_schwarz/ZNB20.py new file mode 100644 index 00000000000..d151cc78ffd --- /dev/null +++ b/qcodes/instrument_drivers/rohde_schwarz/ZNB20.py @@ -0,0 +1,3 @@ +# Ensuring backwards compatibility + +from .ZNB import ZNB as ZNB20 From 41ce6d8ed67f0e037e73c2a6416c9c8e8a16d187 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 14:33:01 +0200 Subject: [PATCH 02/18] Faster qdac (#53) (#730) * Faster qdac (#53) * add support for faster setting of qdac voltage * SR830 fail early in buffered readout if butffer is not full as expected * add option to qdac to ignore return read on voltage set This ivery experimental and likely to break if you are not careful * Remove option to not readback in voltage set. This has too many potential issues and may result in crashes * Add fast voltage set to channel qdac driver too * fix stupid error --- qcodes/instrument_drivers/QDev/QDac.py | 17 +++++++++++++---- qcodes/instrument_drivers/QDev/QDac_channels.py | 11 ++++++++++- .../stanford_research/SR830.py | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/qcodes/instrument_drivers/QDev/QDac.py b/qcodes/instrument_drivers/QDev/QDac.py index f7d1221f247..de07e143f20 100644 --- a/qcodes/instrument_drivers/QDev/QDac.py +++ b/qcodes/instrument_drivers/QDev/QDac.py @@ -156,6 +156,14 @@ def __init__(self, name, address, num_chans=48, update_currents=True): set_cmd='ver {}', val_mapping={True: 1, False: 0}) + self.add_parameter(name='fast_voltage_set', + label='fast voltage set', + parameter_class=ManualParameter, + vals=vals.Bool(), + initial_value=False, + docstring=""""Toggles if DC voltage set should unset any ramp attached to this channel. + If you enable this you should ensure thay any function generator is unset + from the channel before setting voltage""") # Initialise the instrument, all channels DC (unbind func. generators) for chan in self.chan_range: # Note: this call does NOT change the voltage on the channel @@ -208,17 +216,18 @@ def _set_voltage(self, chan, v_set): self._assigned_fgs[chan] = fg # We need .get and not get_latest in case a ramp was interrupted v_start = self.parameters['ch{:02}_v'.format(chan)].get() - time = abs(v_set-v_start)/slope - log.info('Slope: {}, time: {}'.format(slope, time)) + mytime = abs(v_set-v_start)/slope + log.info('Slope: {}, time: {}'.format(slope, mytime)) # Attenuation compensation and syncing # happen inside _rampvoltage - self._rampvoltage(chan, fg, v_start, v_set, time) + self._rampvoltage(chan, fg, v_start, v_set, mytime) else: # compensate for the 0.1 multiplier, if it's on if self.parameters['ch{:02}_vrange'.format(chan)].get_latest() == 1: v_set = v_set*10 # set the mode back to DC in case it had been changed - self.write('wav {} 0 0 0'.format(chan)) + if not self.fast_voltage_set(): + self.write('wav {} 0 0 0'.format(chan)) self.write('set {} {:.6f}'.format(chan, v_set)) def _set_vrange(self, chan, switchint): diff --git a/qcodes/instrument_drivers/QDev/QDac_channels.py b/qcodes/instrument_drivers/QDev/QDac_channels.py index 6ba3ca3ed14..acaf1d545fa 100644 --- a/qcodes/instrument_drivers/QDev/QDac_channels.py +++ b/qcodes/instrument_drivers/QDev/QDac_channels.py @@ -244,6 +244,14 @@ def __init__(self, name, address, num_chans=48, update_currents=True): set_cmd='ver {}', val_mapping={True: 1, False: 0}) + self.add_parameter(name='fast_voltage_set', + label='fast voltage set', + parameter_class=ManualParameter, + vals=vals.Bool(), + initial_value=False, + docstring=""""Toggles if DC voltage set should unset any ramp attached to this channel. + If you enable this you should ensure that any function generator is unset + from the channel before setting voltage""") # Initialise the instrument, all channels DC (unbind func. generators) for chan in self.chan_range: # Note: this call does NOT change the voltage on the channel @@ -314,7 +322,8 @@ def _set_voltage(self, chan, v_set): if self.channels[chan-1].vrange.get_latest() == 1: v_set = v_set*10 # set the mode back to DC in case it had been changed - self.write('wav {} 0 0 0'.format(chan)) + if not self.fast_voltage_set(): + self.write('wav {} 0 0 0'.format(chan)) self.write('set {} {:.6f}'.format(chan, v_set)) def _set_vrange(self, chan, switchint): diff --git a/qcodes/instrument_drivers/stanford_research/SR830.py b/qcodes/instrument_drivers/stanford_research/SR830.py index b7d315cd3c8..1b0827f6231 100644 --- a/qcodes/instrument_drivers/stanford_research/SR830.py +++ b/qcodes/instrument_drivers/stanford_research/SR830.py @@ -108,7 +108,8 @@ def get(self): # parse it realdata = np.fromstring(rawdata, dtype=' Date: Wed, 13 Sep 2017 16:58:28 +0200 Subject: [PATCH 03/18] Corrected Typos (#735) --- qcodes/instrument_drivers/Keysight/Keysight_34465A.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/instrument_drivers/Keysight/Keysight_34465A.py b/qcodes/instrument_drivers/Keysight/Keysight_34465A.py index 95562699cc3..7441db9d235 100644 --- a/qcodes/instrument_drivers/Keysight/Keysight_34465A.py +++ b/qcodes/instrument_drivers/Keysight/Keysight_34465A.py @@ -166,7 +166,7 @@ def __init__(self, name, address, DIG=False, utility_freq=50, silent=False, 0.01e-6] } if DIG: - res_factors['34465A'] = [30e6, 15e-6, 6e-6] + res_factors['34464A'] + res_factors['34465A'] = [30e-6, 15e-6, 6e-6] + res_factors['34465A'] res_factors['34470A'] = [30e-6, 10e-6, 3e-6] + res_factors['34470A'] # Define the extreme aperture time values for the 34465A and 34470A From b09d2ed8648736f51dcd4edb77ac152e364cd5de Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Thu, 14 Sep 2017 14:32:24 +0200 Subject: [PATCH 04/18] Smarter way of speeding up qdac (#737) * Smarter way of speeding up qdac both the new and old way has the same speed up from 32 to 16 ms * remove out of date todo --- .../instrument_drivers/QDev/QDac_channels.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/qcodes/instrument_drivers/QDev/QDac_channels.py b/qcodes/instrument_drivers/QDev/QDac_channels.py index acaf1d545fa..aec6ce74f40 100644 --- a/qcodes/instrument_drivers/QDev/QDac_channels.py +++ b/qcodes/instrument_drivers/QDev/QDac_channels.py @@ -249,9 +249,7 @@ def __init__(self, name, address, num_chans=48, update_currents=True): parameter_class=ManualParameter, vals=vals.Bool(), initial_value=False, - docstring=""""Toggles if DC voltage set should unset any ramp attached to this channel. - If you enable this you should ensure that any function generator is unset - from the channel before setting voltage""") + docstring=""""Deprecated with no functionality""") # Initialise the instrument, all channels DC (unbind func. generators) for chan in self.chan_range: # Note: this call does NOT change the voltage on the channel @@ -322,9 +320,8 @@ def _set_voltage(self, chan, v_set): if self.channels[chan-1].vrange.get_latest() == 1: v_set = v_set*10 # set the mode back to DC in case it had been changed - if not self.fast_voltage_set(): - self.write('wav {} 0 0 0'.format(chan)) - self.write('set {} {:.6f}'.format(chan, v_set)) + # and then set the voltage + self.write('wav {} 0 0 0;set {} {:.6f}'.format(chan, chan, v_set)) def _set_vrange(self, chan, switchint): """ @@ -642,18 +639,20 @@ def write(self, cmd): if you want to use this response, we put it in self._write_response (but only for the very last write call) - Note that this procedure makes it cumbersome to handle the returned - messages from concatenated commands, e.g. 'wav 1 1 1 0;fun 2 1 100 1 1' - Please don't use concatenated commands + In this method we expect to read one termination char per command. As + commands are concatenated by `;` we count the number of concatenated + commands as count(';') + 1 e.g. 'wav 1 1 1 0;fun 2 1 100 1 1' is two + commands. Note that only the response of the last command will be + available in `_write_response` - TODO (WilliamHPNielsen): add automatic de-concatenation of commands. """ if self.debugmode: log.info('Sending command string: {}'.format(cmd)) nr_bytes_written, ret_code = self.visa_handle.write(cmd) self.check_error(ret_code) - self._write_response = self.visa_handle.read() + for _ in range(cmd.count(';')+1): + self._write_response = self.visa_handle.read() def read(self): return self.visa_handle.read() From d3e872daede83e78c441e024ea8af2604334e719 Mon Sep 17 00:00:00 2001 From: sohail chatoor Date: Fri, 15 Sep 2017 14:28:52 +0200 Subject: [PATCH 05/18] Ivvi triggers (#739) * Added environment.yml file * Added requirements to environment.yml * Added requirements to environment.yml * Works under linux now * 1) added american instrument driver from the main qcodes repo 2) added a mock IP module to mock IP instruments 3) added a debug module to debug the AMI430 instrument * the mock IP instrument now remembers the set point * added test for spherical coordinates. Making test for cylindrical coordinates. Work in progress * Added a module for handy converting between spherical, cartesian and cylindrical coodinates. By keeping a prepresnetation of the vector in all three systems, there is less risk of loss of information so that e.g. the field strength can be ramped up given previous supplied phi and theta. The spherical and cylindrical tests seem to work. * additional work * 1) The driver is adhering to the qcodes specs now 2) Made proper tests * 1) The driver is adhering to the qcodes specs now 2) Made proper tests * 1) Made tests to ensure that the set points correctly reach the instruments 2) Made test to verify that the correct error is raised if we intentionally exceed the field limits * Added a test to verify that ramp downs are performed first * Added the ability to define more complex regions for safe set points. Tests to verify this are also included. * Re-wrote the instrument mocker so we have a cleaner design. * changing how mocking works * Mocking no longer requires sockets and threading. Major rewrite of the mocking mechanism * Mocking no longer requires sockets and threading. Major rewrite of the mocking mechanism * further clean up of the mocking mechanism. * Solved a bug which raised a value error when asking for spherical coordinates. Added tests to verify if we can get spherical coordinates. Also, when getting single field values, return a single float instead of an array of length one * 1) Added tests for the field vector 2) Added test to verify that the correct warnings are raised if we change the default maximum ramp rate 3) Added test to verify that an error is raised if the ramp rate larger then the maximum * Added a notebook showing the results of the testing of the new AMI430 driver * * Processed Jens comments, except changing the doc string format and pep8 conventions. Will do that tomorrow * Processed the rest of Jens comments * Added a driver for the Rigol DS4034. I can get a basic waveform with this driver but a lot of work still needs to be done. * Added a ArrayParameter class * Made a get_wave_form function which also returns times. * added parameters to measure amplitude * Added a driver for the Rigol DS4034. Should be more usable now * Added a parameter to send trigger to the IVVI * saving example with SR830 driver * removed the Rigol DS4043 as there is a seperate PR for that * reverted SR830 example to its previous state * Trigger should be a function rather then a parameter. --- qcodes/instrument_drivers/QuTech/IVVI.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qcodes/instrument_drivers/QuTech/IVVI.py b/qcodes/instrument_drivers/QuTech/IVVI.py index a08e0df3af5..39d3b455e83 100644 --- a/qcodes/instrument_drivers/QuTech/IVVI.py +++ b/qcodes/instrument_drivers/QuTech/IVVI.py @@ -116,6 +116,11 @@ def __init__(self, name, address, reset=False, numdacs=16, dac_step=10, label='Dac voltages', get_cmd=self._get_dacs) + self.add_function( + 'trigger', + call_cmd=self._send_trigger + ) + # initialize pol_num, the voltage offset due to the polarity self.pol_num = np.zeros(self._numdacs) for i in range(int(self._numdacs / 4)): @@ -468,6 +473,11 @@ def get_func(): return fun(ch) return get_func + def _send_trigger(self): + msg = bytes([2, 6]) + self.write(msg) + self.read() # Flush the buffer, else the command will only work the first time. + def round_dac(self, value, dacname=None): """ Round a value to the interal precision of the instrument From a8bc48482ce80973f32f1ee2d99896e06ffa81bc Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 18 Sep 2017 16:26:45 +0200 Subject: [PATCH 06/18] Release/0.1.7 (#746) * Docs: Changelog 0.1.7 * Bump version --- docs/changes/0.1.7.rst | 28 ++++++++++++++++++++++++++++ docs/changes/index.rst | 1 + qcodes/version.py | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 docs/changes/0.1.7.rst diff --git a/docs/changes/0.1.7.rst b/docs/changes/0.1.7.rst new file mode 100644 index 00000000000..ecbf79536f7 --- /dev/null +++ b/docs/changes/0.1.7.rst @@ -0,0 +1,28 @@ +Changelog for QCoDeS 0.1.7 +========================== + +New & Improved +-------------- + +- New and Improved drivers: + + - Fixes to DecaDac driver (#713) + - Driver for Oxford Kelvinox (#707) + - Driver for Oxford ILM 200 Helium Level Meter (#706) + - IPS120 Magnet driver (#660) + - New AMI430 driver (#700) + - Driver for tektronics awg5200 (#724) + - Benchmark Keysight DMM software trigger (#729) + - Faster qdac (#730, #737) + - Ivvi triggers (#739) + +- Features: + + - Improved PyQtGraph performance (#712) + - Improve default_parameter_name (#714) + - Function to Close all instruments (#715) + - Automatic Foreground qt window (#716) Requires pywin32 on windows not + installed by default + - Handle snapshot update failing better (#717) + - Cleanup dependencies for easier install (#721) + - Update Slack contact information (#727) diff --git a/docs/changes/index.rst b/docs/changes/index.rst index 8aaf874583b..e2bde6a1b85 100644 --- a/docs/changes/index.rst +++ b/docs/changes/index.rst @@ -8,3 +8,4 @@ Changelogs 0.1.4 <0.1.4> 0.1.5 <0.1.5> 0.1.6 <0.1.6> + 0.1.7 <0.1.7> diff --git a/qcodes/version.py b/qcodes/version.py index 2fb25139f14..124e46203b1 100644 --- a/qcodes/version.py +++ b/qcodes/version.py @@ -1 +1 @@ -__version__ = '0.1.6' +__version__ = '0.1.7' From 15d8af9eb3d440de101aa24f1062bd89bdbb5d10 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 18 Sep 2017 16:45:35 +0200 Subject: [PATCH 07/18] Update zenodo link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c168caec0e5..b9ae6526418 100644 --- a/README.rst +++ b/README.rst @@ -138,5 +138,5 @@ See `License `__. :target: https://travis-ci.org/QCoDeS/Qcodes .. |DOCS| image:: https://img.shields.io/badge/read%20-thedocs-ff66b4.svg :target: http://qcodes.github.io/Qcodes -.. |DOI| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.826079.svg +.. |DOI| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.894477.svg :target: https://doi.org/10.5281/zenodo.843493 From 2e2f8cc275c200ed2af141850b0f3ad85ef8c43b Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 18 Sep 2017 16:46:46 +0200 Subject: [PATCH 08/18] Really update zenodo link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b9ae6526418..547f4eb1171 100644 --- a/README.rst +++ b/README.rst @@ -139,4 +139,4 @@ See `License `__. .. |DOCS| image:: https://img.shields.io/badge/read%20-thedocs-ff66b4.svg :target: http://qcodes.github.io/Qcodes .. |DOI| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.894477.svg - :target: https://doi.org/10.5281/zenodo.843493 + :target: https://doi.org/10.5281/zenodo.894477 From 41530ba9a411e0c5768f59ee8c57718995c69114 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 19 Sep 2017 10:45:58 +0200 Subject: [PATCH 09/18] Fix a number of rst issues (#742) * Add title to notebook for correct rst conversion * SR830 fix rst * kelvinon rst fixes * Fix IPS120 rst cleanup * ILM200 fix rst * ILM200 remove empty args --- .../Qcodes example with AMI430.ipynb | 17 +++++- qcodes/instrument_drivers/oxford/ILM200.py | 55 ++++++------------- qcodes/instrument_drivers/oxford/IPS120.py | 2 +- qcodes/instrument_drivers/oxford/kelvinox.py | 4 +- .../stanford_research/SR830.py | 2 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/docs/examples/driver_examples/Qcodes example with AMI430.ipynb b/docs/examples/driver_examples/Qcodes example with AMI430.ipynb index a78b304679b..47c5af71360 100644 --- a/docs/examples/driver_examples/Qcodes example with AMI430.ipynb +++ b/docs/examples/driver_examples/Qcodes example with AMI430.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# QCoDeS example with AMI430" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -36,7 +43,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "import time\n", @@ -199,7 +208,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Lets test the 3D driver now. \n", @@ -446,7 +457,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.5" + "version": "3.6.2" } }, "nbformat": 4, diff --git a/qcodes/instrument_drivers/oxford/ILM200.py b/qcodes/instrument_drivers/oxford/ILM200.py index 2035e56fe38..2b8c1e52c82 100644 --- a/qcodes/instrument_drivers/oxford/ILM200.py +++ b/qcodes/instrument_drivers/oxford/ILM200.py @@ -32,13 +32,12 @@ def __init__(self, name, address, number=1, **kwargs): """ Initializes the Oxford Instruments ILM 200 Helium Level Meter. - Input: - name (string) : name of the instrument - address (string) : instrument address - number (int) : ISOBUS instrument number - (number=1 is specific to the ILM in F008) + Args: + name (string): name of the instrument + address (string): instrument address + number (int): ISOBUS instrument number (number=1 is specific to the ILM in F008) - Output: + Returns: None """ logging.debug(__name__ + ' : Initializing instrument') @@ -73,10 +72,10 @@ def _execute(self, message): Write a command to the device and read answer. This function writes to the buffer by adding the device number at the front, instead of 'ask'. - Input: + Args: message (str) : write command for the device - Output: + Returns: None """ logging.info( @@ -93,10 +92,10 @@ def _read(self): """ Reads the total bytes in the buffer and outputs as a string. - Input: + Args: None - Output: + Returns: message (str) """ # because protocol has no termination chars the read reads the number @@ -112,7 +111,7 @@ def _read(self): def get_idn(self): """ - Overides the function of Instrument since ILM does not support '*IDN?' + Overrides the function of Instrument since ILM does not support `*IDN?` This string is supposed to be a comma-separated list of vendor, model, serial, and firmware, but @@ -143,12 +142,6 @@ def get_all(self): """ Reads all implemented parameters from the instrument, and updates the wrapper. - - Args: - None - - Returns: - None """ logging.info(__name__ + ' : reading all settings from instrument') self.level.get() @@ -171,7 +164,7 @@ def _get_version(self): Args: None - Return: + Returns: identification (str): should be 'ILM200 Version 1.08 (c) OXFORD 1994\r' """ logging.info(__name__ + ' : Identify the device') @@ -180,7 +173,8 @@ def _get_version(self): def _do_get_level(self): """ Get Helium level of channel 1. - Input: + + Args: None Returns: @@ -193,11 +187,6 @@ def _do_get_level(self): def _do_get_status(self): """ Get status of the device. - Input: - None - - Returns: - None """ logging.info(__name__ + ' : Get status of the device.') result = self._execute('X') @@ -242,12 +231,6 @@ def _do_get_rate(self): def remote(self): """ Set control to remote & locked - - Input: - None - - Output: - None """ logging.info(__name__ + ' : Set control to remote & locked') self.set_remote_status(1) @@ -255,12 +238,6 @@ def remote(self): def local(self): """ Set control to local & locked - - Input: - None - - Output: - None """ logging.info(__name__ + ' : Set control to local & locked') self.set_remote_status(0) @@ -269,14 +246,14 @@ def set_remote_status(self, mode): """ Set remote control status. - Input: + Args: mode(int) : 0 : "Local and locked", 1 : "Remote and locked", 2 : "Local and unlocked", 3 : "Remote and unlocked", - Output: + Returns: None """ status = { @@ -312,7 +289,7 @@ def _do_set_rate(self, rate): """ Set helium meter channel 1 probe rate - Input: + Args: rate(int) : 0 : "SLOW" 1 : "FAST" diff --git a/qcodes/instrument_drivers/oxford/IPS120.py b/qcodes/instrument_drivers/oxford/IPS120.py index a8b9dd77a9d..ed9db142016 100644 --- a/qcodes/instrument_drivers/oxford/IPS120.py +++ b/qcodes/instrument_drivers/oxford/IPS120.py @@ -236,7 +236,7 @@ def close(self): def get_idn(self): """ - Overides the function of Instrument since IPS120 does not support '*IDN?' + Overides the function of Instrument since IPS120 does not support `*IDN?` This string is supposed to be a comma-separated list of vendor, model, serial, and firmware, but semicolon and colon are also common diff --git a/qcodes/instrument_drivers/oxford/kelvinox.py b/qcodes/instrument_drivers/oxford/kelvinox.py index 2a8de1be801..fa46545990f 100644 --- a/qcodes/instrument_drivers/oxford/kelvinox.py +++ b/qcodes/instrument_drivers/oxford/kelvinox.py @@ -168,7 +168,9 @@ def _read(self): def identify(self): """Identify the device - Returns a string of the form `'IGH Version 3.02 (c) OXFORD 1998\r'` + Returns: + a string of the form ``'IGH Version 3.02 (c) OXFORD 1998\\r'`` + """ log.info('Identify the device') return self._execute('V') diff --git a/qcodes/instrument_drivers/stanford_research/SR830.py b/qcodes/instrument_drivers/stanford_research/SR830.py index 1b0827f6231..0c89bb68fb3 100644 --- a/qcodes/instrument_drivers/stanford_research/SR830.py +++ b/qcodes/instrument_drivers/stanford_research/SR830.py @@ -12,7 +12,7 @@ class ChannelBuffer(ArrayParameter): Currently always returns the entire buffer TODO (WilliamHPNielsen): Make it possible to query parts of the buffer. - The instrument natively supports this in its TRCL call. + The instrument natively supports this in its TRCL call. """ def __init__(self, name: str, instrument: 'SR830', channel: int): From e4a22f984c3f5061c2739a189bbb233191024dd6 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 19 Sep 2017 11:14:28 +0200 Subject: [PATCH 10/18] Fix: ip instrument log empty responce and ensure all data is send (#719) * Fix: ip instrument log empty responce As I understand it that is a sign of a closed socket This should probably be an error in the future * Use sendall fixes #748 --- qcodes/instrument/ip.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qcodes/instrument/ip.py b/qcodes/instrument/ip.py index 0b69643648d..4e2d67e3d31 100644 --- a/qcodes/instrument/ip.py +++ b/qcodes/instrument/ip.py @@ -1,8 +1,10 @@ """Ethernet instrument driver class based on sockets.""" import socket +import logging from .base import Instrument +log = logging.getLogger(__name__) class IPInstrument(Instrument): @@ -139,10 +141,14 @@ def set_terminator(self, terminator): def _send(self, cmd): data = cmd + self._terminator - self._socket.send(data.encode()) + self._socket.sendall(data.encode()) def _recv(self): - return self._socket.recv(self._buffer_size).decode() + result = self._socket.recv(self._buffer_size) + if result == b'': + log.warning("Got empty response from Socket recv() " + "Connection broken.") + return result.decode() def close(self): """Disconnect and irreversibly tear down the instrument.""" From 35400a60448101c66337d9b3373b9af1c9666725 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 19 Sep 2017 13:06:39 +0200 Subject: [PATCH 11/18] Unit autoscale matplotlib and pyqtgraph graphs (#733) * Wip for scaling matplotlib plots with unit prefix * fix and make scaling more general * docs * Add pyqtgraph method to fix autoscaling of non std units * remove unused lines * Add rescaling to scaling function to match wrappers * Add typing * call autoscale when a plot is created * Add docstring * Add function to reset limits and scales * fix: dont shadow buildin --- qcodes/plots/base.py | 3 ++ qcodes/plots/pyqtgraph.py | 94 ++++++++++++++++++++++++++++++++++++ qcodes/plots/qcmatplotlib.py | 57 +++++++++++++++++++++- 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/base.py b/qcodes/plots/base.py index 618eb517516..d42748bd54a 100644 --- a/qcodes/plots/base.py +++ b/qcodes/plots/base.py @@ -24,6 +24,9 @@ def __init__(self, interval=1, data_keys='xyz'): self.traces = [] self.data_updaters = set() self.interval = interval + self.standardunits = ['V', 's', 'J', 'W', 'm', 'eV', 'A', 'K', 'g', + 'Hz', 'rad', 'T', 'H', 'F', 'Pa', 'C', 'Ω', 'Ohm', + 'S'] def clear(self): """ diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 97b58517877..5864803bf31 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -1,6 +1,7 @@ """ Live plotting using pyqtgraph """ +from typing import Optional, Dict, Union import numpy as np import pyqtgraph as pg import pyqtgraph.multiprocess as pgmp @@ -80,6 +81,7 @@ def __init__(self, *args, figsize=(1000, 600), interval=0.25, raise err self.win.setBackground(theme[1]) self.win.resize(*figsize) + self._orig_fig_size = figsize self.subplots = [self.add_subplot()] if args or kwargs: @@ -144,6 +146,7 @@ def add_to_plot(self, subplot=1, **kwargs): if prev_default_title == self.win.windowTitle(): self.win.setWindowTitle(self.get_default_title()) + self.fixUnitScaling() def _draw_plot(self, subplot_object, y, x=None, color=None, width=None, antialias=None, **kwargs): @@ -475,3 +478,94 @@ def save(self, filename=None): def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) + + def autorange(self): + """ + Auto range all limits in case they were changed during interactive + plot. Reset colormap if changed and resize window to original size. + """ + for subplot in self.subplots: + vBox = subplot.getViewBox() + vBox.enableAutoRange(vBox.XYAxes) + cmap = None + # resize histogram + for trace in self.traces: + if 'plot_object' in trace.keys(): + if (isinstance(trace['plot_object'], dict) and + 'hist' in trace['plot_object'].keys()): + cmap = trace['plot_object']['cmap'] + maxval = trace['config']['z'].max() + minval = trace['config']['z'].min() + trace['plot_object']['hist'].setLevels(minval, maxval) + trace['plot_object']['hist'].vb.autoRange() + if cmap: + self.set_cmap(cmap) + # set window back to original size + self.win.resize(*self._orig_fig_size) + + def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,int]]]]=None): + """ + Disable SI rescaling if units are not standard units and limit + ranges to data if known. + + Args: + + startranges: The plot can automatically infer the full ranges + array parameters. However it has no knowledge of the + ranges or regular parameters. You can explicitly pass + in the values here as a dict of the form + {'paramtername': {max: value, min:value}} + """ + axismapping = {'x': 'bottom', + 'y': 'left', + 'z': 'right'} + standardunits = self.standardunits + for i, plot in enumerate(self.subplots): + # make a dict mapping axis labels to axis positions + for axis in ('x', 'y', 'z'): + if self.traces[i]['config'].get(axis): + unit = self.traces[i]['config'][axis].unit + if unit not in standardunits: + if axis in ('x', 'y'): + ax = plot.getAxis(axismapping[axis]) + else: + # 2D measurement + # Then we should fetch the colorbar + ax = self.traces[i]['plot_object']['hist'].axis + ax.enableAutoSIPrefix(False) + # because updateAutoSIPrefix called from + # enableAutoSIPrefix doesnt actually take the + # value of the argument into account we have + # to manually replicate the update here + ax.autoSIPrefixScale = 1.0 + ax.setLabel(unitPrefix='') + ax.picture = None + ax.update() + + # set limits either from dataset or + setarr = self.traces[i]['config'][axis].ndarray + arrmin = None + arrmax = None + if not np.all(np.isnan(setarr)): + arrmax = setarr.max() + arrmin = setarr.min() + elif startranges is not None: + try: + paramname = self.traces[i]['config'][axis].full_name + arrmax = startranges[paramname]['max'] + arrmin = startranges[paramname]['min'] + except (IndexError, KeyError): + continue + + if axis == 'x': + rangesetter = getattr(plot.getViewBox(), 'setXRange') + elif axis == 'y': + rangesetter = getattr(plot.getViewBox(), 'setYRange') + else: + rangesetter = None + + if (rangesetter is not None + and arrmin is not None + and arrmax is not None): + rangesetter(arrmin, arrmax) + diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index c5ce80b7c76..6bc0df12850 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -7,6 +7,7 @@ from functools import partial import matplotlib.pyplot as plt +from matplotlib import ticker import numpy as np from matplotlib.transforms import Bbox from numpy.ma import masked_invalid, getmask @@ -399,4 +400,58 @@ def tight_layout(self): Perform a tight layout on the figure. A bit of additional spacing at the top is also added for the title. """ - self.fig.tight_layout(rect=[0, 0, 1, 0.95]) \ No newline at end of file + self.fig.tight_layout(rect=[0, 0, 1, 0.95]) + + def rescale_axis(self): + """ + Rescale axis and units for axis that are in standard units + i.e. V, s J ... to m μ, m + This scales units defined in BasePlot.standardunits only + to avoid prefixes on combined or non standard units + """ + def scale_formatter(i, pos, scale): + return "{0:g}".format(i * scale) + + for i, subplot in enumerate(self.subplots): + for axis in 'x', 'y', 'z': + if self.traces[i]['config'].get(axis): + unit = self.traces[i]['config'][axis].unit + label = self.traces[i]['config'][axis].label + maxval = abs(self.traces[i]['config'][axis].ndarray).max() + units_to_scale = self.standardunits + + # allow values up to a <1000. i.e. nV is used up to 1000 nV + prefixes = ['n', 'μ', 'm', '', 'k', 'M', 'G'] + thresholds = [10**(-6 + 3*n) for n in range(len(prefixes))] + scales = [10**(9 - 3*n) for n in range(len(prefixes))] + + if unit in units_to_scale: + scale = 1 + new_unit = unit + for prefix, threshold, trialscale in zip(prefixes, + thresholds, + scales): + if maxval < threshold: + scale = trialscale + new_unit = prefix + unit + break + # special case the largest + if maxval > thresholds[-1]: + scale = scales[-1] + new_unit = prefixes[-1] + unit + + tx = ticker.FuncFormatter( + partial(scale_formatter, scale=scale)) + new_label = "{} ({})".format(label, new_unit) + if axis in ('x', 'y'): + getattr(subplot, + "{}axis".format(axis)).set_major_formatter( + tx) + getattr(subplot, "set_{}label".format(axis))( + new_label) + else: + subplot.qcodes_colorbar.formatter = tx + subplot.qcodes_colorbar.ax.yaxis.set_major_formatter( + tx) + subplot.qcodes_colorbar.set_label(new_label) + subplot.qcodes_colorbar.update_ticks() From a3a423f1ed16d3502151c1f79fbda6ebc542aa0c Mon Sep 17 00:00:00 2001 From: Dominik Vogel <30660470+Dominik-Vogel@users.noreply.github.com> Date: Wed, 20 Sep 2017 10:08:16 +0200 Subject: [PATCH 12/18] Moved files to upper case folder, created note asking for this standard (#725) --- qcodes/instrument_drivers/{keysight => Keysight}/M3201A.py | 0 qcodes/instrument_drivers/{keysight => Keysight}/M3300A.py | 0 .../{keysight => Keysight}/SD_common/SD_AWG.py | 0 .../{keysight => Keysight}/SD_common/SD_DIG.py | 0 .../{keysight => Keysight}/SD_common/SD_Module.py | 0 qcodes/instrument_drivers/{keysight => Keysight}/test_suite.py | 0 .../instrument_drivers/keysight/Use Upper Case Keysightfolder | 2 ++ 7 files changed, 2 insertions(+) rename qcodes/instrument_drivers/{keysight => Keysight}/M3201A.py (100%) rename qcodes/instrument_drivers/{keysight => Keysight}/M3300A.py (100%) rename qcodes/instrument_drivers/{keysight => Keysight}/SD_common/SD_AWG.py (100%) rename qcodes/instrument_drivers/{keysight => Keysight}/SD_common/SD_DIG.py (100%) rename qcodes/instrument_drivers/{keysight => Keysight}/SD_common/SD_Module.py (100%) rename qcodes/instrument_drivers/{keysight => Keysight}/test_suite.py (100%) create mode 100644 qcodes/instrument_drivers/keysight/Use Upper Case Keysightfolder diff --git a/qcodes/instrument_drivers/keysight/M3201A.py b/qcodes/instrument_drivers/Keysight/M3201A.py similarity index 100% rename from qcodes/instrument_drivers/keysight/M3201A.py rename to qcodes/instrument_drivers/Keysight/M3201A.py diff --git a/qcodes/instrument_drivers/keysight/M3300A.py b/qcodes/instrument_drivers/Keysight/M3300A.py similarity index 100% rename from qcodes/instrument_drivers/keysight/M3300A.py rename to qcodes/instrument_drivers/Keysight/M3300A.py diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_AWG.py b/qcodes/instrument_drivers/Keysight/SD_common/SD_AWG.py similarity index 100% rename from qcodes/instrument_drivers/keysight/SD_common/SD_AWG.py rename to qcodes/instrument_drivers/Keysight/SD_common/SD_AWG.py diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_DIG.py b/qcodes/instrument_drivers/Keysight/SD_common/SD_DIG.py similarity index 100% rename from qcodes/instrument_drivers/keysight/SD_common/SD_DIG.py rename to qcodes/instrument_drivers/Keysight/SD_common/SD_DIG.py diff --git a/qcodes/instrument_drivers/keysight/SD_common/SD_Module.py b/qcodes/instrument_drivers/Keysight/SD_common/SD_Module.py similarity index 100% rename from qcodes/instrument_drivers/keysight/SD_common/SD_Module.py rename to qcodes/instrument_drivers/Keysight/SD_common/SD_Module.py diff --git a/qcodes/instrument_drivers/keysight/test_suite.py b/qcodes/instrument_drivers/Keysight/test_suite.py similarity index 100% rename from qcodes/instrument_drivers/keysight/test_suite.py rename to qcodes/instrument_drivers/Keysight/test_suite.py diff --git a/qcodes/instrument_drivers/keysight/Use Upper Case Keysightfolder b/qcodes/instrument_drivers/keysight/Use Upper Case Keysightfolder new file mode 100644 index 00000000000..99af1006b40 --- /dev/null +++ b/qcodes/instrument_drivers/keysight/Use Upper Case Keysightfolder @@ -0,0 +1,2 @@ +In the repository there are two different Keysight folders: "Keysight" and "keysight". Under some operating systems they appear as one. If you should see two, use the upper case one. For more information go to: +https://github.com/QCoDeS/Qcodes/pull/725 From 2ef1e761c5fde09bb1a0bbc008bc80c541759b1c Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Thu, 21 Sep 2017 15:59:36 +0200 Subject: [PATCH 13/18] Small fixes to plot scaling (#753) * the matplotlib colorbar uses two different colorbars only set the top one or strange effects happen * Pyqt make it optional and off by default to scale colorbar --- qcodes/plots/pyqtgraph.py | 11 +++++++---- qcodes/plots/qcmatplotlib.py | 2 -- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 5864803bf31..bfd4c200072 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -479,10 +479,13 @@ def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) - def autorange(self): + def autorange(self, reset_colorbar: bool=False): """ Auto range all limits in case they were changed during interactive plot. Reset colormap if changed and resize window to original size. + Args: + reset_colorbar: Should the limits and colorscale of the colorbar + be reset. Off by default """ for subplot in self.subplots: vBox = subplot.getViewBox() @@ -492,7 +495,8 @@ def autorange(self): for trace in self.traces: if 'plot_object' in trace.keys(): if (isinstance(trace['plot_object'], dict) and - 'hist' in trace['plot_object'].keys()): + 'hist' in trace['plot_object'].keys() and + reset_colorbar): cmap = trace['plot_object']['cmap'] maxval = trace['config']['z'].max() minval = trace['config']['z'].min() @@ -517,8 +521,7 @@ def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,i {'paramtername': {max: value, min:value}} """ axismapping = {'x': 'bottom', - 'y': 'left', - 'z': 'right'} + 'y': 'left'} standardunits = self.standardunits for i, plot in enumerate(self.subplots): # make a dict mapping axis labels to axis positions diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index 6bc0df12850..74ef71df970 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -451,7 +451,5 @@ def scale_formatter(i, pos, scale): new_label) else: subplot.qcodes_colorbar.formatter = tx - subplot.qcodes_colorbar.ax.yaxis.set_major_formatter( - tx) subplot.qcodes_colorbar.set_label(new_label) subplot.qcodes_colorbar.update_ticks() From b10a15ed2139e4ddfe7452de6dad4966b4e24436 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Fri, 22 Sep 2017 13:44:46 +0200 Subject: [PATCH 14/18] Fix decadac driver (#756) Add channels to slots correctly so that they get snapshotted --- qcodes/instrument_drivers/Harvard/Decadac.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qcodes/instrument_drivers/Harvard/Decadac.py b/qcodes/instrument_drivers/Harvard/Decadac.py index 1af43f7cc9e..cdc216a939b 100644 --- a/qcodes/instrument_drivers/Harvard/Decadac.py +++ b/qcodes/instrument_drivers/Harvard/Decadac.py @@ -304,11 +304,11 @@ def __init__(self, parent, name, slot, min_val=-5, max_val=5): self._VERSA_EEPROM_available = self._parent._VERSA_EEPROM_available # Create a list of channels in the slot - self.channels = ChannelList(self, "Slot_Channels", DacChannel) + channels = ChannelList(self, "Slot_Channels", DacChannel) for i in range(4): - self.channels.append(DacChannel(self, "Chan{}".format(i), i, - min_val=min_val, max_val=max_val)) - + channels.append(DacChannel(self, "Chan{}".format(i), i, + min_val=min_val, max_val=max_val)) + self.add_submodule("channels", channels) # Set the slot mode. Valid modes are: # Off: Channel outputs are disconnected from the input, grounded with 10MOhm. # Fine: 2-channel mode. Channels 0 and 1 are output, use 2 and 3 for fine From a6d43594fa85b1fa8a43e20cf907a4bd73ddb6e0 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 25 Sep 2017 13:46:57 +0200 Subject: [PATCH 15/18] ZNB don't snapshot traces (#758) They are slow and may not even function : --- qcodes/instrument_drivers/rohde_schwarz/ZNB.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qcodes/instrument_drivers/rohde_schwarz/ZNB.py b/qcodes/instrument_drivers/rohde_schwarz/ZNB.py index 792e121bc2e..2c98b4f2dfb 100644 --- a/qcodes/instrument_drivers/rohde_schwarz/ZNB.py +++ b/qcodes/instrument_drivers/rohde_schwarz/ZNB.py @@ -345,6 +345,12 @@ def _set_center(self, val): self.trace.set_sweep(start, stop, npts) self.trace_mag_phase.set_sweep(start, stop, npts) + def snapshot_base(self, update=False, params_to_skip_update=None): + if params_to_skip_update is None: + params_to_skip_update = ('trace', 'trace_mag_phase') + snap = super().snapshot_base(update=update, + params_to_skip_update=params_to_skip_update) + return snap class ZNB(VisaInstrument): """ From e2e0c3e2ed8ddb295ade8ffce9d13cf0e393cb58 Mon Sep 17 00:00:00 2001 From: Andrea Corna Date: Mon, 25 Sep 2017 15:42:37 +0200 Subject: [PATCH 16/18] Make 'device clear' optional in VisaInstrument initialization (#752) --- qcodes/instrument/visa.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/qcodes/instrument/visa.py b/qcodes/instrument/visa.py index ed7d3276e3a..9bb0f754eb0 100644 --- a/qcodes/instrument/visa.py +++ b/qcodes/instrument/visa.py @@ -28,6 +28,8 @@ class VisaInstrument(Instrument): terminator: Read termination character(s) to look for. Default ''. + device_clear: Perform a device clear. Default True. + metadata (Optional[Dict]): additional static metadata to add to this instrument's JSON snapshot. @@ -38,7 +40,7 @@ class VisaInstrument(Instrument): visa_handle (pyvisa.resources.Resource): The communication channel. """ - def __init__(self, name, address=None, timeout=5, terminator='', **kwargs): + def __init__(self, name, address=None, timeout=5, terminator='', device_clear=True, **kwargs): super().__init__(name, **kwargs) self.add_parameter('timeout', @@ -49,6 +51,8 @@ def __init__(self, name, address=None, timeout=5, terminator='', **kwargs): vals.Enum(None))) self.set_address(address) + if device_clear: + self.device_clear() self.set_terminator(terminator) self.timeout.set(timeout) @@ -76,6 +80,10 @@ def set_address(self, address): resource_manager = visa.ResourceManager() self.visa_handle = resource_manager.open_resource(address) + self._address = address + + def device_clear(self): + """Clear the buffers of the device""" # Serial instruments have a separate flush method to clear their buffers # which behaves differently to clear. This is particularly important @@ -84,7 +92,6 @@ def set_address(self, address): self.visa_handle.flush(vi_const.VI_READ_BUF_DISCARD | vi_const.VI_WRITE_BUF_DISCARD) else: self.visa_handle.clear() - self._address = address def set_terminator(self, terminator): r""" From dccf3bb20488842eec99e6e7ccd6cffb7fd5ed53 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 25 Sep 2017 15:46:55 +0200 Subject: [PATCH 17/18] Backport expose location counter (#749) --- qcodes/data/location.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qcodes/data/location.py b/qcodes/data/location.py index 91af8594cf1..f794bcf1806 100644 --- a/qcodes/data/location.py +++ b/qcodes/data/location.py @@ -87,7 +87,7 @@ class FormatLocation: def __init__(self, fmt=None, fmt_date=None, fmt_time=None, fmt_counter=None, record=None): - #TODO(giulioungaretti) this should be + # TODO(giulioungaretti) this should be # FormatLocation.default_fmt self.fmt = fmt or self.default_fmt self.fmt_date = fmt_date or '%Y-%m-%d' @@ -96,6 +96,7 @@ def __init__(self, fmt=None, fmt_date=None, fmt_time=None, self.base_record = record self.formatter = SafeFormatter() + self.counter = 0 for testval in (1, 23, 456, 7890): if self._findint(self.fmt_counter.format(testval)) != testval: raise ValueError('fmt_counter must produce a correct integer ' @@ -163,7 +164,8 @@ def __call__(self, io, record=None): cnt = self._findint(f[len(head):]) existing_count = max(existing_count, cnt) - format_record['counter'] = self.fmt_counter.format(existing_count + 1) + self.counter = existing_count + 1 + format_record['counter'] = self.fmt_counter.format(self.counter) location = self.formatter.format(loc_fmt, **format_record) return location From 36271959705f3bc25a9ebfe3911e78ba27a0e799 Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Thu, 28 Sep 2017 19:40:01 +1000 Subject: [PATCH 18/18] feat: add MatPlot guide (#762) * feat: add MatPlot guide * feat: add event handling section * feat: final note on widgets * doc: add info on interfacing with matplotlib --- .../Comprehensive Plotting How-To.ipynb | 5297 +++++++++++++++++ 1 file changed, 5297 insertions(+) create mode 100644 docs/examples/Comprehensive Plotting How-To.ipynb diff --git a/docs/examples/Comprehensive Plotting How-To.ipynb b/docs/examples/Comprehensive Plotting How-To.ipynb new file mode 100644 index 00000000000..93ce6b56c25 --- /dev/null +++ b/docs/examples/Comprehensive Plotting How-To.ipynb @@ -0,0 +1,5297 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comprehensive Plotting How-To" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import qcodes as qc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting data in QCoDeS can be done using either MatPlot or QTPlot, with matplotlib and pyqtgraph as backends, respectively. \n", + "MatPlot and QTPlot tailor these plotting backends to QCoDeS, providing many features.\n", + "For example, when plotting a DataArray in a DataSet, the corresponding ticks, labels, etc. are automatically added to the plot.\n", + "Both MatPlot and QTPlot support live plotting while a measurement is running\n", + "\n", + "One of the main differences between the two backends is that matplotlib is more strongly integrated with Jupyter Notebook, while pyqtgraph uses the PyQT GUI.\n", + "For matplotlib, this has the advantage that plots can be displayed within a notebook (though it also has a gui).\n", + "The advantage of pyqtgraph is that it can be easily embedded in PyQT GUI's.\n", + "\n", + "This guide aims to provide a detailed guide on how to use each of the two plotting tools." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MatPlot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The QCoDeS MatPlot relies on the matplotlib package, which is quite similar to Matlab's plotting tools.\n", + "It integrates nicely with Jupyter notebook, and as a result, interactive plots can be displayed within a notebook using the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simple 1D sweep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a first example, we perform a simple 1D sweep.\n", + "We create two trivial parameters, one for measuring a value, and the other for sweeping the value of the measured parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "p_measure = qc.ManualParameter(name='measured_val')\n", + "p_sweep = qc.StandardParameter(name='sweep_val', set_cmd=p_measure.set)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we perform a measurement, and attach the `update` method of the `plot` object to the loop, resulting in live plotting.\n", + "Note that the resulting plot automatically has the correct x values and labels." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Started at 2017-09-28 11:01:54\n", + "DataSet:\n", + " location = 'test_plotting_1D_2'\n", + " | | | \n", + " Setpoint | sweep_val_set | sweep_val | (21,)\n", + " Measured | measured_val | measured_val | (21,)\n", + " Measured | measured_val_2 | measured_val_2 | (21,)\n", + "Finished at 2017-09-28 11:02:07\n" + ] + } + ], + "source": [ + "loop = qc.Loop(\n", + " p_sweep.sweep(0, 20, step=1), delay=0.5).each(\n", + " p_measure,\n", + " p_measure2)\n", + "data = loop.get_data_set('test_plotting_1D_2')\n", + "\n", + "# Create plot for measured data\n", + "plot = qc.MatPlot([data.measured_val, data.measured_val_2], data.measured_val, data.measured_val_2)\n", + "# Attach updating of plot to loop\n", + "loop.with_bg_task(plot.update)\n", + "\n", + "loop.run();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data arrays don't all have to be passed along during initialization of the MatPlot instance.\n", + "We can access the subplots of the plot object as if the plot was a list (e.g. `plot[0]` would give you the first subplot).\n", + "To illustrate this, the example below results in the same plot as above." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Started at 2017-09-28 11:02:21\n", + "DataSet:\n", + " location = 'test_plotting_2D'\n", + " | | | \n", + " Setpoint | sweep_val_set | sweep_val | (21,)\n", + " Setpoint | sweep_val_2_set | sweep_val_2 | (21, 11)\n", + " Measured | measured_val | measured_val | (21, 11)\n", + "Finished at 2017-09-28 11:02:56\n" + ] + } + ], + "source": [ + "loop = qc.Loop(\n", + " p_sweep.sweep(0, 20, step=1), delay=0.5).loop(\n", + " p_sweep2.sweep(0, 10, step=1), delay=0.1).each(\n", + " p_measure)\n", + "data = loop.get_data_set('test_plotting_2D')\n", + "\n", + "# Create plot for measured data\n", + "plot = qc.MatPlot([*data.measured_val], data.measured_val)\n", + "# Attach updating of plot to loop\n", + "loop.with_bg_task(plot.update)\n", + "\n", + "loop.run();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the example above, the colorbar can be accessed via `plot[1].qcodes_colorbar`.\n", + "This can be useful when you want to modify the colorbar (e.g. change the color limits `clim`).\n", + "\n", + "Note that the above plot was updated every time an inner loop was completed. \n", + "This is because the update method was attached to the outer loop.\n", + "If you instead want it to update within an outer loop, you have to attach it to an inner loop: `loop[0].with_bg_task(plot.update)` (`loop[0]` is the first action of the outer loop, which is the inner loop)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interfacing with Matplotlib\n", + "As Matplot is built directly on top of Matplotlib, you can use standard Matplotlib functions which are readily available online in Matplotlib documentation as well as StackOverflow and similar sites. Here, we first perform the same measurement and obtain the corresponding figure:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def handle_close(event):\n", + " print('Plot closed')\n", + " \n", + "plot = qc.MatPlot()\n", + "plot.fig.canvas.mpl_connect('close_event', handle_close);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On a related note, matplotlib also has widgets that can be added to plots, allowing additional interactivity with the dataset.\n", + "An example would be adding a slider to show 2D plots of a 3D dataset (e.g. https://matplotlib.org/examples/widgets/slider_demo.html)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## QTPlot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To be written" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + }, + "toc": { + "hide_others": true, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "toc_position": {}, + "toc_section_display": "block", + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}