diff --git a/.gitignore b/.gitignore index bfd2716c0a3..e30ba9c4a04 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,15 @@ target/ *.py~ tmp/ +# DS-store +*.DS_Store + +# Sublime text automation +*.sublime-project +*.sublime-workspace + +venv/ + docs/examples/data/* + +*.DS_Store diff --git a/docs/conf.py b/docs/conf.py index 503191080fa..08b43e2df7b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -383,6 +383,7 @@ except ImportError as e: print(e) autodoc_mock_imports.append('pyspcm') +autodoc_mock_imports.append('scipy') autodoc_mock_imports.append('zhinst') autodoc_mock_imports.append('zhinst.utils') autodoc_mock_imports.append('keysightSD1') diff --git a/docs/examples/MockAlazarExperiment.ipynb b/docs/examples/MockAlazarExperiment.ipynb new file mode 100644 index 00000000000..aff772d9ef2 --- /dev/null +++ b/docs/examples/MockAlazarExperiment.ipynb @@ -0,0 +1,1542 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "%matplotlib nbagg" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "from qcodes.instrument.mock import MockInstrument\n", + "from qcodes.instrument.parameter import MultiParameter\n", + "import qcodes as qc\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from qcodes.utils.helpers import is_sequence_of" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "class ExpandingAlazarArrayMultiParameter(MultiParameter):\n", + " \n", + " def __init__(self, name, names, labels, units, shapes, instrument, \n", + " setpoints, setpoint_names, setpoint_labels, setpoint_units,\n", + " start, stop, step):\n", + " self._start = start\n", + " self._stop = stop\n", + " self._step = step\n", + " super().__init__(name,\n", + " names=names,\n", + " units=units, \n", + " labels=labels,\n", + " shapes=shapes,\n", + " instrument=instrument,\n", + " setpoints=setpoints,\n", + " setpoint_names=setpoint_names,\n", + " setpoint_labels=setpoint_labels,\n", + " setpoint_units=setpoint_units)\n", + " \n", + " def set_setpoints_and_labels(self):\n", + " arraysetpoints = (tuple(np.arange(self.start,self.stop,self.step)),)\n", + " setpoints = [arraysetpoints]\n", + " names = ['rawname']\n", + " labels = ['rawlabel']\n", + " setpoint_name = ('timename',)\n", + " setpoint_names=[setpoint_name]\n", + " setpoint_label = ('timelabel',)\n", + " setpoint_labels=[setpoint_label]\n", + " setpoint_unit = ('s',)\n", + " setpoint_units = [setpoint_unit]\n", + " units = ['v']\n", + " shape = (len(arraysetpoints[0]),)\n", + " shapes = [shape]\n", + " for i, demod_freq in enumerate(self._instrument.demod_freqs):\n", + " names.append(\"demod_freq_{}_name\".format(i))\n", + " labels.append(\"demod_freq_{}_label\".format(i))\n", + " units.append('v')\n", + " shapes.append(shape)\n", + " setpoints.append(arraysetpoints)\n", + " setpoint_names.append(setpoint_name)\n", + " setpoint_labels.append(setpoint_label)\n", + " setpoint_units.append(setpoint_unit)\n", + " self.names = tuple(names)\n", + " self.labels = tuple(labels)\n", + " self.units = tuple(units)\n", + " self.shapes = tuple(shapes)\n", + " self.setpoints = tuple(setpoints)\n", + " self.setpoint_names = tuple(setpoint_names)\n", + " self.setpoint_labels = tuple(setpoint_labels)\n", + " self.setpoint_units = tuple(setpoint_units)\n", + " \n", + " def get(self):\n", + " x = np.asarray(self.setpoints[0][0])\n", + " y = np.zeros_like(x)\n", + " arrays = [y]\n", + " for demod_freq in self._instrument.demod_freqs:\n", + " arrays.append(np.sin(demod_freq*x))\n", + " return tuple(arrays)\n", + " \n", + " @property\n", + " def start(self):\n", + " return self._start\n", + " \n", + " @start.setter\n", + " def start(self, value):\n", + " self._start = value\n", + " self.set_setpoints_and_labels()\n", + " \n", + " @property\n", + " def step(self):\n", + " return self._step\n", + " \n", + " @step.setter\n", + " def step(self, value):\n", + " self._step = value\n", + " self.set_setpoints_and_labels()\n", + "\n", + " @property\n", + " def stop(self):\n", + " return self._stop\n", + " \n", + " @stop.setter\n", + " def stop(self, value):\n", + " self._stop = value\n", + " self.set_setpoints_and_labels()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "class MockAlazar(MockInstrument):\n", + " \n", + " \n", + " def __init__(self, name='MyMockAlazar'):\n", + " \n", + " super().__init__(name, model=StupidModel())\n", + " self.demod_freqs = []\n", + " start=0\n", + " stop=2*np.pi\n", + " step=0.01\n", + " setpoints = ((tuple(np.arange(start,stop,step)),),)\n", + " self.add_parameter(name='acquisition',\n", + " names=('rawname',),\n", + " labels=('rawlabel',),\n", + " units=('v',),\n", + " shapes=((629,),),\n", + " setpoints=setpoints,\n", + " setpoint_names=(('timename',),),\n", + " setpoint_labels=(('timelabel',),),\n", + " setpoint_units=(('s',),),\n", + " start=start,\n", + " stop=stop,\n", + " step=step,\n", + " parameter_class=ExpandingAlazarArrayMultiParameter)\n", + " \n", + " def add_demodulator(self, demod_freq):\n", + " if demod_freq not in self.demod_freqs:\n", + " self.demod_freqs.append(demod_freq)\n", + " self.acquisition.set_setpoints_and_labels()\n", + "\n", + " def remove_demodulator(self, demod_freq):\n", + " if demod_freq in self.demod_freqs:\n", + " self.demod_freqs.pop(self.demod_freqs.index(demod_freq))\n", + " self.acquisition.set_setpoints_and_labels()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "class StupidModel:\n", + " \"\"\"\n", + " For reasons a MockInstument must have a model. \n", + " The aim of this model is to do exactly nothing\n", + " \"\"\"\n", + " def write(self):\n", + " pass\n", + " \n", + " def ask(self):\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "a = MockAlazar()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "629" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(a.acquisition.setpoints[0][0])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-02-21/#026_{name}_14-12-28'\n", + " | | | \n", + " Setpoint | timename_set | timename | (629,)\n", + " Measured | MyMockAlazar_rawname | rawname | (629,)\n", + "acquired at 2017-02-21 14:12:28\n" + ] + } + ], + "source": [ + "data = qc.Measure(a.acquisition).run()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'__class__': 'qcodes.data.data_array.DataArray',\n", + " 'action_indices': (0, 0),\n", + " 'array_id': 'MyMockAlazar_rawname',\n", + " 'instrument': '__main__.MockAlazar',\n", + " 'instrument_name': 'MyMockAlazar',\n", + " 'is_setpoint': False,\n", + " 'label': 'rawlabel',\n", + " 'labels': ('rawlabel',),\n", + " 'name': 'rawname',\n", + " 'names': ('rawname',),\n", + " 'setpoint_labels': (('timelabel',),),\n", + " 'setpoint_names': (('timename',),),\n", + " 'shape': (629,),\n", + " 'unit': 'v',\n", + " 'units': ('v',)}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data.metadata['arrays']['MyMockAlazar_rawname']" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'__class__': 'qcodes.data.data_array.DataArray',\n", + " 'action_indices': (0,),\n", + " 'array_id': 'timename_set',\n", + " 'is_setpoint': True,\n", + " 'label': 'timelabel',\n", + " 'name': 'timename',\n", + " 'shape': (629,),\n", + " 'unit': 's'}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data.metadata['arrays']['timename_set']" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1887" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "629*3" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "a.add_demodulator(1)\n", + "a.add_demodulator(2)\n", + "a.add_demodulator(3)\n", + "a.acquisition.stop = 4*np.pi\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-02-21/#029_{name}_14-13-26'\n", + " | | | \n", + " Setpoint | timename_set | timename | (1257,)\n", + " Measured | MyMockAlazar_rawname | rawname | (1257,)\n", + " Measured | MyMockAlazar_demod_freq_0_name | demod_freq_0_name | (1257,)\n", + " Measured | MyMockAlazar_demod_freq_1_name | demod_freq_1_name | (1257,)\n", + " Measured | MyMockAlazar_demod_freq_2_name | demod_freq_2_name | (1257,)\n", + "acquired at 2017-02-21 14:13:27\n" + ] + } + ], + "source": [ + "data2 = qc.Measure(a.acquisition).run()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true, + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'__class__': 'qcodes.data.data_array.DataArray',\n", + " 'action_indices': (0,),\n", + " 'array_id': 'timename_set',\n", + " 'is_setpoint': True,\n", + " 'label': 'timelabel',\n", + " 'name': 'timename',\n", + " 'shape': (1257,),\n", + " 'unit': 's'}" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data2.metadata['arrays']['timename_set']" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'__class__': 'qcodes.data.data_array.DataArray',\n", + " 'action_indices': (0, 1),\n", + " 'array_id': 'MyMockAlazar_demod_freq_0_name',\n", + " 'instrument': '__main__.MockAlazar',\n", + " 'instrument_name': 'MyMockAlazar',\n", + " 'is_setpoint': False,\n", + " 'label': 'demod_freq_0_label',\n", + " 'labels': ('rawlabel',\n", + " 'demod_freq_0_label',\n", + " 'demod_freq_1_label',\n", + " 'demod_freq_2_label'),\n", + " 'name': 'demod_freq_0_name',\n", + " 'names': ('rawname',\n", + " 'demod_freq_0_name',\n", + " 'demod_freq_1_name',\n", + " 'demod_freq_2_name'),\n", + " 'setpoint_labels': (('timelabel',),\n", + " ('timelabel',),\n", + " ('timelabel',),\n", + " ('timelabel',)),\n", + " 'setpoint_names': (('timename',),\n", + " ('timename',),\n", + " ('timename',),\n", + " ('timename',)),\n", + " 'shape': (1257,),\n", + " 'unit': 'v',\n", + " 'units': ('v', 'v', 'v', 'v')}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data2.metadata['arrays']['MyMockAlazar_demod_freq_0_name']" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "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('Close figure', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\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", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\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" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Finally show that this instrument also works within a loop\n", + "dummy = parameter.ManualParameter(name=\"dummy\")\n", + "data = qc.Loop(dummy[0:50:1]).each(acquisition_controller.acquisition).run(name='AlazarTest')\n", + "qc.MatPlot(data.acquisition_controller_acquisition)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "data" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.5.2" + }, + "widgets": { + "state": {}, + "version": "1.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/docs/examples/Qcodes example with Alazar ATS9360 new controller.ipynb b/docs/examples/Qcodes example with Alazar ATS9360 new controller.ipynb new file mode 100644 index 00000000000..b760d68435d --- /dev/null +++ b/docs/examples/Qcodes example with Alazar ATS9360 new controller.ipynb @@ -0,0 +1,4950 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "### Qcodes example notebook for Alazar card ATS9360 and acq controllers" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No loop running\n" + ] + } + ], + "source": [ + "import qcodes as qc\n", + "import qcodes.instrument.parameter as parameter\n", + "import qcodes.instrument_drivers.AlazarTech.ATS9360 as ATSdriver\n", + "from qcodes.instrument_drivers.AlazarTech.acq_controllers import ATS9360Controller\n", + "from qcodes.station import Station\n", + "\n", + "import logging\n", + "# logging.basicConfig(filename='example.log',level=logging.INFO)\n", + "\n", + "qc.halt_bg()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "NB: See ATS9360 example notebook for general commands " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n", + "WARNING:root:`units` is deprecated for the `Parameter` class, use `unit` instead. \n" + ] + }, + { + "data": { + "text/plain": [ + "{'CPLD_version': '25.16',\n", + " 'SDK_version': '5.10.22',\n", + " 'asopc_type': '1645511520',\n", + " 'bits_per_sample': 12,\n", + " 'driver_version': '5.10.22',\n", + " 'firmware': None,\n", + " 'latest_cal_date': '25-01-17',\n", + " 'max_samples': 4294967294,\n", + " 'memory_size': '4294967294',\n", + " 'model': 'ATS9360',\n", + " 'pcie_link_speed': '0.5GB/s',\n", + " 'pcie_link_width': '8',\n", + " 'serial': '970396',\n", + " 'vendor': 'AlazarTech'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create the ATS9360 instrument\n", + "alazar = ATSdriver.AlazarTech_ATS9360(name='Alazar')\n", + "# Print all information about this Alazar card\n", + "alazar.get_idn()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "944" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alazar._handle" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [], + "source": [ + "# Configure all settings in the Alazar card\n", + "alazar.config(clock_source='INTERNAL_CLOCK',\n", + " sample_rate=500000000,\n", + " clock_edge='CLOCK_EDGE_RISING',\n", + " decimation=1,\n", + " coupling=['DC','DC'],\n", + " channel_range=[.4,.4],\n", + " impedance=[50,50],\n", + " trigger_operation='TRIG_ENGINE_OP_J',\n", + " trigger_engine1='TRIG_ENGINE_J',\n", + " trigger_source1='EXTERNAL',\n", + " trigger_slope1='TRIG_SLOPE_POSITIVE',\n", + " trigger_level1=160,\n", + " trigger_engine2='TRIG_ENGINE_K',\n", + " trigger_source2='DISABLE',\n", + " trigger_slope2='TRIG_SLOPE_POSITIVE',\n", + " trigger_level2=128,\n", + " external_trigger_coupling='DC',\n", + " external_trigger_range='ETR_2V5',\n", + " trigger_delay=0,\n", + " timeout_ticks=0,\n", + " aux_io_mode='AUX_IN_AUXILIARY', # AUX_IN_TRIGGER_ENABLE for seq mode on\n", + " aux_io_param='NONE' # TRIG_SLOPE_POSITIVE for seq mode on\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Example 1\n", + "\n", + "Pulls the raw data the alazar acquires averaged over records and buffers." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true, + "scrolled": true + }, + "outputs": [], + "source": [ + "# Create the acquisition controller which will take care of the data handling and tell it which \n", + "# alazar instrument to talk to. Explicitly pass the default options to the Alazar.\n", + "# Dont integrate over samples but avarage over records\n", + "myctrl = ATS9360Controller(name='my_controller', alazar_name='Alazar',\n", + " integrate_samples=False, average_records=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Put the Alazar and the controller in a station so we ensure that all parameters are captured" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Error getting or interpreting *IDN?: ''\n" + ] + } + ], + "source": [ + "station = qc.Station(alazar, myctrl)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This controller is designed to be highlevel and it is not possible to directly set number of records, buffers and samples. The number of samples is indirecly controlled by the integration time and integration delay and the number of averages controls the number of buffers and records acquired" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "myctrl.int_delay(2e-7)\n", + "myctrl.int_time(3.1e-6)\n", + "myctrl.num_avg(1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000 records per buffer set by num_avg\n", + "1664 samples per record set by int_time and int_delay\n" + ] + } + ], + "source": [ + "print(\"{} records per buffer set by num_avg\".format(myctrl.records_per_buffer()))\n", + "print(\"{} samples per record set by int_time and int_delay\".format(myctrl.samples_per_record()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Per default the output will contain an array called raw_output of unprocessed data" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-03-08/#059_{name}_15-55-30'\n", + " | | | \n", + " Setpoint | time_set | time | (1664,)\n", + " Measured | my_controller_raw_output | raw_output | (1664,)\n", + "acquired at 2017-03-08 15:55:31\n" + ] + } + ], + "source": [ + "# Measure this \n", + "data1 = qc.Measure(myctrl.acquisition).run()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "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" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot = qc.MatPlot()\n", + "plot.add(data2.my_controller_demod_freq_0_mag)\n", + "plot.fig.axes[0].legend()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "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": [ + "plot = qc.MatPlot(data6.my_controller_rec_demod_freq_5_mag)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "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" + }, + { + "data": { + "text/plain": [ + "(, DataSet:\n", + " location = '/Users/william/sourcecodes/qcodes-jenshnielsen/docs/examples/TutorialExperiment/TutorialSample/011'\n", + " | | | \n", + " Setpoint | dac_ch1_set | ch1 | (25,)\n", + " Measured | dmm_voltage | voltage | (25,))" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# For instance, sweep a dac voltage and record with the dmm\n", + "\n", + "do1d(dac.ch1, 0, 15, 25, 0.1, dmm.voltage)\n", + "# Call signature: do1d(thing_to_sweep,\n", + "# sweep_start,\n", + "# sweep_stop,\n", + "# number_of_sweep_points,\n", + "# delay_per_point,\n", + "# thing_to_measure)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Started at 2017-06-14 13:31:41\n", + "DataSet:\n", + " location = '/Users/william/sourcecodes/qcodes-jenshnielsen/docs/examples/TutorialExperiment/TutorialSample/012'\n", + " | | | \n", + " Setpoint | dac_ch1_set | ch1 | (25,)\n", + " Measured | dmm_voltage | voltage | (25,)\n", + "Finished at 2017-06-14 13:31:43\n" + ] + }, + { + "data": { + "text/plain": [ + "(None, DataSet:\n", + " location = '/Users/william/sourcecodes/qcodes-jenshnielsen/docs/examples/TutorialExperiment/TutorialSample/012'\n", + " | | | \n", + " Setpoint | dac_ch1_set | ch1 | (25,)\n", + " Measured | dmm_voltage | voltage | (25,))" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# If you can't be bothered to look at the plot (if, for instance, you are running many do1ds),\n", + "# the live plotting can be disabled\n", + "do1d(dac.ch1, 0, 15, 25, 0.1, dmm.voltage, do_plots=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "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" + }, + { + "data": { + "text/plain": [ + "(, DataSet:\n", + " location = '/Users/william/sourcecodes/qcodes-jenshnielsen/docs/examples/TutorialExperiment/TutorialSample/013'\n", + " | | | \n", + " Setpoint | dac_ch1_set | ch1 | (25,)\n", + " Setpoint | dac_ch2_set | ch2 | (25, 10)\n", + " Measured | dmm_voltage | voltage | (25, 10))" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's ramp those dac voltages!\n", + "\n", + "do2d(dac.ch1, 0, 10, 25, 0.01, # outer loop\n", + " dac.ch2, 2, 5, 10, 0.01, # inner loop, i.e. this runs for each point of the outer loop\n", + " dmm.voltage)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `do2d` function also produces a DataSet, this time noticeably with **two** setpoint arrays." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/docs/examples/driver_examples/Qcodes example with Rohde Schwarz ZNB 20.ipynb b/docs/examples/driver_examples/Qcodes example with Rohde Schwarz ZNB 20.ipynb new file mode 100644 index 00000000000..d43ef5c6c03 --- /dev/null +++ b/docs/examples/driver_examples/Qcodes example with Rohde Schwarz ZNB 20.ipynb @@ -0,0 +1,3553 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Qcodes example with Rohde Schwarz ZN20" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib nbagg\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import qcodes as qc" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import qcodes.instrument_drivers.rohde_schwarz.ZNB20 as vna" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to: Rohde-Schwarz ZNB20-2Port (serial:1311601062101551, firmware:2.82) in 0.17s\n" + ] + } + ], + "source": [ + "vna = vna.ZNB20('VNA', 'TCPIP0::192.168.15.100::inst0::INSTR', server_name=None)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "station = qc.Station(vna)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The QCoDes driver for the Rohde Schwarz ZNB 20 is setup with 4 channels each containing one trace and reprecenting the 4 standars S parameters (S11, S12, S21 and S22). For each S parameter you can define a frequency sweep as and the power of the rf source i.e for s11 sweep from 100 KHz to 6 MHz in 100 steps:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "vna.start11(100e3)\n", + "vna.stop11(6e6)\n", + "vna.npts11(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With a power of -30 dBm" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "vna.power11(-30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can meassure a frequency trace, first remembering to turn on the rf source. This produces both a linear magnitude and phase" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " location = 'data/2017-05-24/#047_{name}_14-35-53'\n", + " | | | \n", + " Setpoint | frequency_set | frequency | (100,)\n", + " Measured | VNA_s11_magnitude | s11_magnitude | (100,)\n", + " Measured | VNA_s11_phase | s11_phase | (100,)\n", + "acquired at 2017-05-24 14:35:53\n" + ] + } + ], + "source": [ + "vna.rf_on()\n", + "data = qc.Measure(vna.trace11).run()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "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": [ + "data = qc.Measure(vna.trace11).run()\n", + "plot = qc.MatPlot(subplots=(1,2))\n", + "plot.add(data.VNA_s11_magnitude, subplot=1)\n", + "plot.add(data.VNA_s11_phase, subplot=2)\n", + "plot.fig.tight_layout(rect=(0, 0, 1, 0.95))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also measure the magniture in dB." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " location = 'data/2017-05-24/#049_{name}_14-35-58'\n", + " | | | \n", + " Measured | VNA_tracedb11 | tracedb11 | (100,)\n", + "acquired at 2017-05-24 14:35:58\n" + ] + }, + { + "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": [ + "plot = qc.MatPlot(subplots=(1,2))\n", + "plot.add(data1.VNA_s11_magnitude, subplot=1)\n", + "plot.add(data1.VNA_s11_phase, subplot=2)\n", + "plot.fig.tight_layout(rect=(0, 0, 1, 0.95))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/qcodes/__init__.py b/qcodes/__init__.py index 542d2c10359..6de60ce54b6 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -27,7 +27,6 @@ 'try "from qcodes.plots.qcmatplotlib import MatPlot" ' 'to see the full error') - from qcodes.station import Station from qcodes.loops import Loop, active_loop, active_data_set from qcodes.measure import Measure diff --git a/qcodes/data/io.py b/qcodes/data/io.py index 4b7a82c8588..4b2fd45381c 100644 --- a/qcodes/data/io.py +++ b/qcodes/data/io.py @@ -91,7 +91,6 @@ def open(self, filename, mode, encoding=None): raise ValueError('mode {} not allowed in IO managers'.format(mode)) filepath = self.to_path(filename) - # make directories if needed dirpath = os.path.dirname(filepath) if not os.path.exists(dirpath): diff --git a/qcodes/data/location.py b/qcodes/data/location.py index 91af8594cf1..b342b0bfbf0 100644 --- a/qcodes/data/location.py +++ b/qcodes/data/location.py @@ -85,6 +85,7 @@ class FormatLocation: default_fmt = config['core']['default_fmt'] + def __init__(self, fmt=None, fmt_date=None, fmt_time=None, fmt_counter=None, record=None): #TODO(giulioungaretti) this should be @@ -95,7 +96,7 @@ def __init__(self, fmt=None, fmt_date=None, fmt_time=None, self.fmt_counter = fmt_counter or '{:03}' 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 diff --git a/qcodes/instrument_drivers/AlazarTech/ATS.py b/qcodes/instrument_drivers/AlazarTech/ATS.py index 98ba18443e9..d839a678a4c 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS.py @@ -1,6 +1,7 @@ import ctypes import logging import numpy as np +import time import os from qcodes.instrument.base import Instrument @@ -10,14 +11,14 @@ # TODO(damazter) (C) logging # these items are important for generalizing this code to multiple alazar cards -# TODO(damazter) (W) remove 8 bits per sample requirement # TODO(damazter) (W) some alazar cards have a different number of channels :( -# this driver only works with 2-channel cards # TODO(damazter) (S) tests to do: # acquisition that would overflow the board if measurement is not stopped # quickly enough. can this be solved by not reposting the buffers? +# TODO (natalie) make logging vs print vs nothing decisions + class AlazarTech_ATS(Instrument): """ @@ -33,7 +34,7 @@ class AlazarTech_ATS(Instrument): name: name for this instrument, passed to the base instrument system_id: target system id for this instrument board_id: target board id within the system for this instrument - dll_path: string contianing the path of the ATS driver dll + dll_path: string containing the path of the ATS driver dll """ # override dll_path in your init script or in the board constructor @@ -193,13 +194,14 @@ def find_boards(cls, dll_path=None): boards.append(cls.get_board_info(dll, system_id, board_id)) return boards + # TODO(nataliejpg) this needs fixing..., dll can't be a string @classmethod def get_board_info(cls, dll, system_id, board_id): """ Get the information from a connected Alazar board Args: - dll (string): path of the Alazar driver dll + dll (CDLL): CTypes CDLL system_id: id of the Alazar system board_id: id of the board within the alazar system @@ -213,7 +215,6 @@ def get_board_info(cls, dll, system_id, board_id): - max_samples - bits_per_sample """ - # make a temporary instrument for this board, to make it easier # to get its info board = cls('temp', system_id=system_id, board_id=board_id, @@ -232,7 +233,15 @@ def get_board_info(cls, dll, system_id, board_id): def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs): super().__init__(name, **kwargs) - self._ATS_dll = ctypes.cdll.LoadLibrary(dll_path or self.dll_path) + self._ATS_dll = None + + if os.name == 'nt': + self._ATS_dll = ctypes.cdll.LoadLibrary(dll_path or self.dll_path) + else: + raise Exception("Unsupported OS") + + # TODO (W) make the board id more general such that more than one card + # per system configurations are supported self._handle = self._ATS_dll.AlazarGetBoardBySystemID(system_id, board_id) @@ -266,56 +275,60 @@ def get_idn(self): board_kind = self._board_names[ self._ATS_dll.AlazarGetBoardKind(self._handle)] - major = np.array([0], dtype=np.uint8) - minor = np.array([0], dtype=np.uint8) - revision = np.array([0], dtype=np.uint8) + max_s, bps = self._get_channel_info(self._handle) + + major = ctypes.c_uint8(0) + minor = ctypes.c_uint8(0) + revision = ctypes.c_uint8(0) self._call_dll('AlazarGetCPLDVersion', self._handle, - major.ctypes.data, - minor.ctypes.data) - cpld_ver = str(major[0])+"."+str(minor[0]) + ctypes.byref(major), + ctypes.byref(minor)) + cpld_ver = str(major.value) + "." + str(minor.value) self._call_dll('AlazarGetDriverVersion', - major.ctypes.data, - minor.ctypes.data, - revision.ctypes.data) - driver_ver = str(major[0])+"."+str(minor[0])+"."+str(revision[0]) + ctypes.byref(major), + ctypes.byref(minor), + ctypes.byref(revision)) + driver_ver = str(major.value)+"."+str(minor.value)+"."+str(revision.value) self._call_dll('AlazarGetSDKVersion', - major.ctypes.data, - minor.ctypes.data, - revision.ctypes.data) - sdk_ver = str(major[0])+"."+str(minor[0])+"."+str(revision[0]) + ctypes.byref(major), + ctypes.byref(minor), + ctypes.byref(revision)) + sdk_ver = str(major.value)+"."+str(minor.value)+"."+str(revision.value) - value = np.array([0], dtype=np.uint32) + value = ctypes.c_uint32(0) self._call_dll('AlazarQueryCapability', - self._handle, 0x10000024, 0, value.ctypes.data) - serial = str(value[0]) + self._handle, 0x10000024, 0, ctypes.byref(value)) + serial = str(value.value) self._call_dll('AlazarQueryCapability', - self._handle, 0x10000026, 0, value.ctypes.data) - latest_cal_date = (str(value[0])[0:2] + "-" + - str(value[0])[2:4] + "-" + - str(value[0])[4:6]) + self._handle, 0x10000026, 0, ctypes.byref(value)) + Capabilitystring = str(value.value) + latest_cal_date = (Capabilitystring[0:2] + "-" + + Capabilitystring[2:4] + "-" + + Capabilitystring[4:6]) self._call_dll('AlazarQueryCapability', - self._handle, 0x1000002A, 0, value.ctypes.data) - memory_size = str(value[0]) + self._handle, 0x1000002A, 0, ctypes.byref(value)) + memory_size = str(value.value) self._call_dll('AlazarQueryCapability', - self._handle, 0x1000002C, 0, value.ctypes.data) - asopc_type = str(value[0]) + self._handle, 0x1000002C, 0, ctypes.byref(value)) + asopc_type = str(value.value) # see the ATS-SDK programmer's guide # about the encoding of the link speed self._call_dll('AlazarQueryCapability', - self._handle, 0x10000030, 0, value.ctypes.data) - pcie_link_speed = str(value[0]*2.5/10)+"GB/s" + self._handle, 0x10000030, 0, ctypes.byref(value)) + pcie_link_speed = str(value.value * 2.5 / 10) + "GB/s" self._call_dll('AlazarQueryCapability', - self._handle, 0x10000031, 0, value.ctypes.data) - pcie_link_width = str(value[0]) - + self._handle, 0x10000031, 0, ctypes.byref(value)) + pcie_link_width = str(value.value) return {'firmware': None, 'model': board_kind, + 'max_samples': max_s, + 'bits_per_sample': bps, 'serial': serial, 'vendor': 'AlazarTech', 'CPLD_version': cpld_ver, @@ -328,6 +341,7 @@ def get_idn(self): 'pcie_link_width': pcie_link_width} def config(self, clock_source=None, sample_rate=None, clock_edge=None, + external_sample_rate=None, decimation=None, coupling=None, channel_range=None, impedance=None, bwlimit=None, trigger_operation=None, trigger_engine1=None, trigger_source1=None, @@ -335,7 +349,8 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, trigger_engine2=None, trigger_source2=None, trigger_slope2=None, trigger_level2=None, external_trigger_coupling=None, external_trigger_range=None, - trigger_delay=None, timeout_ticks=None): + trigger_delay=None, timeout_ticks=None, aux_io_mode=None, + aux_io_param=None): """ configure the ATS board and set the corresponding parameters to the appropriate values. @@ -371,6 +386,7 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, self._set_if_present('clock_source', clock_source) self._set_if_present('sample_rate', sample_rate) + self._set_if_present('external_sample_rate', external_sample_rate) self._set_if_present('clock_edge', clock_edge) self._set_if_present('decimation', decimation) @@ -396,10 +412,26 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, external_trigger_range) self._set_if_present('trigger_delay', trigger_delay) self._set_if_present('timeout_ticks', timeout_ticks) + self._set_if_present('aux_io_mode', aux_io_mode) + self._set_if_present('aux_io_param', aux_io_param) # endregion + # handle that external clock and internal clock uses + # two different ways of setting the sample rate. + # We use the matching one and mark the order one + # as up to date since it's not being pushed to + # the instrument at any time and is never used + if clock_source == 'EXTERNAL_CLOCK_10MHz_REF': + sample_rate = self.external_sample_rate + if 'sample_rate' in self.parameters: + self.parameters['sample_rate']._set_updated() + elif clock_source == 'INTERNAL_CLOCK': + sample_rate = self.sample_rate + if 'external_sample_rate' in self.parameters: + self.parameters['external_sample_rate']._set_updated() + self._call_dll('AlazarSetCaptureClock', - self._handle, self.clock_source, self.sample_rate, + self._handle, self.clock_source, sample_rate, self.clock_edge, self.decimation) for i in range(1, self.channels + 1): @@ -408,9 +440,10 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, self.parameters['coupling' + str(i)], self.parameters['channel_range' + str(i)], self.parameters['impedance' + str(i)]) - self._call_dll('AlazarSetBWLimit', - self._handle, i, - self.parameters['bwlimit' + str(i)]) + if bwlimit is not None: + self._call_dll('AlazarSetBWLimit', + self._handle, i, + self.parameters['bwlimit' + str(i)]) self._call_dll('AlazarSetTriggerOperation', self._handle, self.trigger_operation, @@ -429,14 +462,18 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None, self._call_dll('AlazarSetTriggerTimeOut', self._handle, self.timeout_ticks) - # TODO(damazter) (W) config AUXIO + self._call_dll('AlazarConfigureAuxIO', + self._handle, self.aux_io_mode, + self.aux_io_param) def _get_channel_info(self, handle): - bps = np.array([0], dtype=np.uint8) # bps bits per sample - max_s = np.array([0], dtype=np.uint32) # max_s memory size in samples + bps = ctypes.c_uint8(0) # bps bits per sample + max_s = ctypes.c_uint32(0) # max_s memory size in samples self._call_dll('AlazarGetChannelInfo', - handle, max_s.ctypes.data, bps.ctypes.data) - return max_s[0], bps[0] + handle, + ctypes.byref(max_s), + ctypes.byref(bps)) + return max_s.value, bps.value def acquire(self, mode=None, samples_per_record=None, records_per_buffer=None, buffers_per_acquisition=None, @@ -501,11 +538,6 @@ def acquire(self, mode=None, samples_per_record=None, # Abort any previous measurement self._call_dll('AlazarAbortAsyncRead', self._handle) - # get channel info - max_s, bps = self._get_channel_info(self._handle) - if bps != 8: - raise Exception('Only 8 bits per sample supported at this moment') - # Set record size for NPT mode if mode == 'NPT': pretriggersize = 0 # pretriggersize is 0 for NPT always @@ -546,7 +578,7 @@ def acquire(self, mode=None, samples_per_record=None, if (samples_per_record % buffers_per_acquisition != 0): logging.warning('buffers_per_acquisition is not a divisor of ' 'samples per record which it should be in ' - 'TS mode, rounding down in samples per buffer ' + 'Ts mode, rounding down in samples per buffer ' 'calculation') samples_per_buffer = int(samples_per_record / buffers_per_acquisition) @@ -574,14 +606,37 @@ def acquire(self, mode=None, samples_per_record=None, self.interleave_samples._set_updated() self.get_processed_data._set_updated() + # bytes per sample + max_s, bps = self._get_channel_info(self._handle) + # TODO(JHN) Why +7 I guess its to do ceil division? + bytes_per_sample = (bps + 7) // 8 + # bytes per record + bytes_per_record = bytes_per_sample * samples_per_record + + # channels + if self.channel_selection._get_byte() == 3: + number_of_channels = 2 + else: + number_of_channels = 1 + + # bytes per buffer + bytes_per_buffer = (bytes_per_record * + records_per_buffer * number_of_channels) + # create buffers for acquisition + # TODO(nataliejpg) should this be > 1 (as intuitive) or > 8 as in alazar sample code? + # the alazar code probably uses bits per sample? + sample_type = ctypes.c_uint8 + if bytes_per_sample > 1: + sample_type = ctypes.c_uint16 + self.clear_buffers() # make sure that allocated_buffers <= buffers_per_acquisition if (self.allocated_buffers._get_byte() > self.buffers_per_acquisition._get_byte()): - print("'allocated_buffers' should be smaller than or equal to" - "'buffers_per_acquisition'. Defaulting 'allocated_buffers' to" - "" + str(self.buffers_per_acquisition._get_byte())) + logging.warning("'allocated_buffers' should be <= " + "'buffers_per_acquisition'. Defaulting 'allocated_buffers'" + " to " + str(self.buffers_per_acquisition._get_byte())) self.allocated_buffers._set( self.buffers_per_acquisition._get_byte()) @@ -589,37 +644,44 @@ def acquire(self, mode=None, samples_per_record=None, for k in range(allocated_buffers): try: - self.buffer_list.append(Buffer(bps, samples_per_buffer, - number_of_channels)) + self.buffer_list.append(Buffer(sample_type, bytes_per_buffer)) except: self.clear_buffers() raise # post buffers to Alazar + # print("made buffer list length " + str(len(self.buffer_list))) for buf in self.buffer_list: + self._ATS_dll.AlazarPostAsyncBuffer.argtypes = [ctypes.c_uint32, ctypes.c_void_p, ctypes.c_uint32] self._call_dll('AlazarPostAsyncBuffer', - self._handle, buf.addr, buf.size_bytes) + self._handle, ctypes.cast(buf.addr, ctypes.c_void_p), buf.size_bytes) self.allocated_buffers._set_updated() # -----start capture here----- acquisition_controller.pre_start_capture() + start = time.clock() # Keep track of when acquisition started # call the startcapture method self._call_dll('AlazarStartCapture', self._handle) + logging.info("Capturing %d buffers." % buffers_per_acquisition) acquisition_controller.pre_acquire() + # buffer handling from acquisition buffers_completed = 0 + bytes_transferred = 0 buffer_timeout = self.buffer_timeout._get_byte() self.buffer_timeout._set_updated() buffer_recycling = (self.buffers_per_acquisition._get_byte() > self.allocated_buffers._get_byte()) - while buffers_completed < self.buffers_per_acquisition._get_byte(): + while (buffers_completed < self.buffers_per_acquisition._get_byte()): + # Wait for the buffer at the head of the list of available + # buffers to be filled by the board. buf = self.buffer_list[buffers_completed % allocated_buffers] self._call_dll('AlazarWaitAsyncBufferComplete', - self._handle, buf.addr, buffer_timeout) + self._handle, ctypes.cast(buf.addr, ctypes.c_void_p), buffer_timeout) # TODO(damazter) (C) last series of buffers must be handled # exceptionally @@ -628,12 +690,12 @@ def acquire(self, mode=None, samples_per_record=None, # if buffers must be recycled, extract data and repost them # otherwise continue to next buffer - if buffer_recycling: acquisition_controller.handle_buffer(buf.buffer) self._call_dll('AlazarPostAsyncBuffer', - self._handle, buf.addr, buf.size_bytes) + self._handle, ctypes.cast(buf.addr, ctypes.c_void_p), buf.size_bytes) buffers_completed += 1 + bytes_transferred += buf.size_bytes # stop measurement here self._call_dll('AlazarAbortAsyncRead', self._handle) @@ -651,6 +713,24 @@ def acquire(self, mode=None, samples_per_record=None, for p in self.parameters.values(): p.get() + # Compute the total transfer time, and display performance information. + transfer_time_sec = time.clock() - start + # print("Capture completed in %f sec" % transfer_time_sec) + buffers_per_sec = 0 + bytes_per_sec = 0 + records_per_sec = 0 + if transfer_time_sec > 0: + buffers_per_sec = buffers_completed / transfer_time_sec + bytes_per_sec = bytes_transferred / transfer_time_sec + records_per_sec = (records_per_buffer * + buffers_completed / transfer_time_sec) + logging.info("Captured %d buffers (%f buffers per sec)" % + (buffers_completed, buffers_per_sec)) + logging.info("Captured %d records (%f records per sec)" % + (records_per_buffer * buffers_completed, records_per_sec)) + logging.info("Transferred %d bytes (%f bytes per sec)" % + (bytes_transferred, bytes_per_sec)) + # return result return acquisition_controller.post_acquire() @@ -678,7 +758,7 @@ def _call_dll(self, func_name, *args): args_out = [] update_params = [] for arg in args: - if isinstance(arg,AlazarParameter): + if isinstance(arg, AlazarParameter): args_out.append(arg._get_byte()) update_params.append(arg) else: @@ -686,10 +766,14 @@ def _call_dll(self, func_name, *args): # run the function func = getattr(self._ATS_dll, func_name) - return_code = func(*args_out) + try: + return_code = func(*args_out) + except Exception as e: + logging.error(e) + raise # check for errors - if (return_code != self._success) and (return_code !=518): + if (return_code != self._success) and (return_code != 518): # TODO(damazter) (C) log error argrepr = repr(args_out) @@ -714,10 +798,14 @@ def clear_buffers(self): This method uncommits all buffers that were committed by the driver. This method only has to be called when the acquistion crashes, otherwise the driver will uncommit the buffers itself - :return: None + + Returns: + None + """ for b in self.buffer_list: b.free_mem() + logging.info("buffers cleared") self.buffer_list = [] def signal_to_volt(self, channel, signal): @@ -793,7 +881,7 @@ def __init__(self, name=None, label=None, unit=None, instrument=None, # TODO(damazter) (S) test this validator vals = validators.Enum(*byte_to_value_dict.values()) - super().__init__(name=name, label=label, units=unit, vals=vals, + super().__init__(name=name, label=label, unit=unit, vals=vals, instrument=instrument) self.instrument = instrument self._byte = None @@ -813,7 +901,10 @@ def __init__(self, name=None, label=None, unit=None, instrument=None, def get(self): """ This method returns the name of the value set for this parameter - :return: value + + Returns: + value + """ # TODO(damazter) (S) test this exception if self._uptodate_flag is False: @@ -829,7 +920,10 @@ def get(self): def _get_byte(self): """ this method gets the byte representation of the value of the parameter - :return: byte representation + + Returns: + byte representation + """ return self._byte @@ -838,8 +932,12 @@ def _set(self, value): This method sets the value of this parameter This method is private to ensure that all values in the instruments are up to date always - :param value: the new value (e.g. 'NPT', 0.5, ...) - :return: None + + Args: + value: the new value (e.g. 'NPT', 0.5, ...) + Returns: + None + """ # TODO(damazter) (S) test this validation @@ -856,67 +954,80 @@ def _set_updated(self): can go wrong. Do not use this function if you do not know what you are doing - :return: None + + Returns: + None """ self._uptodate_flag = True class Buffer: - """ - This class represents a single buffer used for the data acquisition + """Buffer suitable for DMA transfers. - Args: - bits_per_sample: the number of bits needed to store a sample - samples_per_buffer: the number of samples needed per buffer(per channel) - number_of_channels: the number of channels that will be stored in the - buffer - """ - def __init__(self, bits_per_sample, samples_per_buffer, - number_of_channels): - if bits_per_sample != 8: - raise Exception("Buffer: only 8 bit per sample supported") - if os.name != 'nt': - raise Exception("Buffer: only Windows supported at this moment") - self._allocated = True + AlazarTech digitizers use direct memory access (DMA) to transfer + data from digitizers to the computer's main memory. This class + abstracts a memory buffer on the host, and ensures that all the + requirements for DMA transfers are met. - # try to allocate memory - mem_commit = 0x1000 - page_readwrite = 0x4 + Buffer export a 'buffer' member, which is a NumPy array view + of the underlying memory buffer - self.size_bytes = samples_per_buffer * number_of_channels + Args: + c_sample_type (ctypes type): The datatype of the buffer to create. + size_bytes (int): The size of the buffer to allocate, in bytes. + """ + def __init__(self, c_sample_type, size_bytes): + self.size_bytes = size_bytes + + npSampleType = { + ctypes.c_uint8: np.uint8, + ctypes.c_uint16: np.uint16, + ctypes.c_uint32: np.uint32, + ctypes.c_int32: np.int32, + ctypes.c_float: np.float32 + }.get(c_sample_type, 0) + + bytes_per_sample = { + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_int32: 4, + ctypes.c_float: 4 + }.get(c_sample_type, 0) - # for documentation please see: - # https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx - ctypes.windll.kernel32.VirtualAlloc.argtypes = [ - ctypes.c_void_p, ctypes.c_long, ctypes.c_long, ctypes.c_long] - ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p - self.addr = ctypes.windll.kernel32.VirtualAlloc( - 0, ctypes.c_long(self.size_bytes), mem_commit, page_readwrite) - if self.addr is None: + self._allocated = True + self.addr = None + if os.name == 'nt': + MEM_COMMIT = 0x1000 + PAGE_READWRITE = 0x4 + ctypes.windll.kernel32.VirtualAlloc.argtypes = [ctypes.c_void_p, ctypes.c_long, ctypes.c_long, ctypes.c_long] + ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p + self.addr = ctypes.windll.kernel32.VirtualAlloc( + 0, ctypes.c_long(size_bytes), MEM_COMMIT, PAGE_READWRITE) + else: self._allocated = False - e = ctypes.windll.kernel32.GetLastError() - raise Exception("Memory allocation error: " + str(e)) + raise Exception("Unsupported OS") - ctypes_array = (ctypes.c_uint8 * - self.size_bytes).from_address(self.addr) - self.buffer = np.frombuffer(ctypes_array, dtype=np.uint8) + ctypes_array = (c_sample_type * + (size_bytes // bytes_per_sample)).from_address(self.addr) + self.buffer = np.frombuffer(ctypes_array, dtype=npSampleType) + self.ctypes_buffer = ctypes_array pointer, read_only_flag = self.buffer.__array_interface__['data'] def free_mem(self): """ uncommit memory allocated with this buffer object - :return: None """ - mem_release = 0x8000 - - # for documentation please see: - # https://msdn.microsoft.com/en-us/library/windows/desktop/aa366892(v=vs.85).aspx - ctypes.windll.kernel32.VirtualFree.argtypes = [ - ctypes.c_void_p, ctypes.c_long, ctypes.c_long] - ctypes.windll.kernel32.VirtualFree.restype = ctypes.c_int - ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(self.addr), 0, - mem_release) + self._allocated = False + if os.name == 'nt': + MEM_RELEASE = 0x8000 + ctypes.windll.kernel32.VirtualFree.argtypes = [ctypes.c_void_p, ctypes.c_long, ctypes.c_long] + ctypes.windll.kernel32.VirtualFree.restype = ctypes.c_int + ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(self.addr), 0, MEM_RELEASE); + else: + self._allocated = True + raise Exception("Unsupported OS") def __del__(self): """ @@ -924,7 +1035,6 @@ def __del__(self): is the last chance to uncommit the memory to prevent a memory leak. This method is not very reliable so users should not rely on this functionality - :return: """ if self._allocated: self.free_mem() @@ -941,24 +1051,24 @@ class AcquisitionController(Instrument): The basic structure of an acquisition is: - - call to AlazarTech_ATS.acquire internal configuration - - call to acquisitioncontroller.pre_start_capture + - Call to AlazarTech_ATS.acquire internal configuration + - Call to acquisitioncontroller.pre_start_capture - Call to the start capture of the Alazar board - - call to acquisitioncontroller.pre_acquire - - loop over all buffers that need to be acquired + - Call to acquisitioncontroller.pre_acquire + - Loop over all buffers that need to be acquired dump each buffer to acquisitioncontroller.handle_buffer (only if buffers need to be recycled to finish the acquisiton) - - dump remaining buffers to acquisitioncontroller.handle_buffer + - Dump remaining buffers to acquisitioncontroller.handle_buffer alazar internals - - return acquisitioncontroller.post_acquire + - Return acquisitioncontroller.post_acquire Attributes: _alazar: a reference to the alazar instrument driver """ def __init__(self, name, alazar_name, **kwargs): """ - :param alazar_name: The name of the alazar instrument on the server - :return: nothing + Args: + alazar_name: The name of the alazar instrument on the server """ super().__init__(name, **kwargs) self._alazar = self.find_instrument(alazar_name, diff --git a/qcodes/instrument_drivers/AlazarTech/ATS9360.py b/qcodes/instrument_drivers/AlazarTech/ATS9360.py new file mode 100644 index 00000000000..9ded066f68c --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/ATS9360.py @@ -0,0 +1,336 @@ +from .ATS import AlazarTech_ATS, AlazarParameter +from .acquisition_parameters import EffectiveSampleRateParameter +from qcodes.utils import validators + + +class AlazarTech_ATS9360(AlazarTech_ATS): + """ + This class is the driver for the ATS9360 board + it inherits from the ATS base class + + TODO(nataliejpg): + - add clock source options and sample rate options + (problem being that byte_to_value_dict of + sample_rate relies on value of clock_source) + + """ + samples_divisor = 128 + + def __init__(self, name, **kwargs): + dll_path = 'C:\\WINDOWS\\System32\\ATSApi.dll' + super().__init__(name, dll_path=dll_path, **kwargs) + + # add parameters + + # ----- Parameters for the configuration of the board ----- + self.add_parameter(name='clock_source', + parameter_class=AlazarParameter, + label='Clock Source', + unit=None, + value='INTERNAL_CLOCK', + byte_to_value_dict={1: 'INTERNAL_CLOCK', + 2: 'FAST_EXTERNAL_CLOCK', + 7: 'EXTERNAL_CLOCK_10MHz_REF'}) + self.add_parameter(name='external_sample_rate', + parameter_class=AlazarParameter, + label='External Sample Rate', + unit='S/s', + value=500000000) + self.add_parameter(name='sample_rate', + parameter_class=AlazarParameter, + label='Internal Sample Rate', + unit='S/s', + value=500000000, + byte_to_value_dict={0x00000001: 1000, + 0x00000002: 2000, + 0x00000004: 5000, + 0x00000008: 10000, + 0x0000000A: 20000, + 0x0000000C: 50000, + 0x0000000E: 100000, + 0x00000010: 200000, + 0x00000012: 500000, + 0x00000014: 1000000, + 0x00000018: 2000000, + 0x0000001A: 5000000, + 0x0000001C: 10000000, + 0x0000001E: 20000000, + 0x00000021: 25000000, + 0x00000022: 50000000, + 0x00000024: 100000000, + 0x00000025: 125000000, + 0x00000026: 160000000, + 0x00000027: 180000000, + 0x00000028: 200000000, + 0x0000002B: 250000000, + 0x00000030: 500000000, + 0x00000032: 800000000, + 0x00000035: 1000000000, + 0x00000037: 1200000000, + 0x0000003A: 1500000000, + 0x0000003D: 1800000000, + 0x0000003F: 2000000000, + 0x0000006A: 2400000000, + 0x00000075: 3000000000, + 0x0000007B: 3600000000, + 0x00000080: 4000000000, + }) + self.add_parameter(name='clock_edge', + parameter_class=AlazarParameter, + label='Clock Edge', + unit=None, + value='CLOCK_EDGE_RISING', + byte_to_value_dict={0: 'CLOCK_EDGE_RISING', + 1: 'CLOCK_EDGE_FALLING'}) + self.add_parameter(name='decimation', + parameter_class=AlazarParameter, + label='Decimation', + unit=None, + value=1, + vals=validators.Ints(0, 100000)) + + for i in ['1', '2']: + self.add_parameter(name='coupling' + i, + parameter_class=AlazarParameter, + label='Coupling channel ' + i, + unit=None, + value='DC', + byte_to_value_dict={1: 'AC', 2: 'DC'}) + self.add_parameter(name='channel_range' + i, + parameter_class=AlazarParameter, + label='Range channel ' + i, + unit='V', + value=0.4, + byte_to_value_dict={7: 0.4}) + self.add_parameter(name='impedance' + i, + parameter_class=AlazarParameter, + label='Impedance channel ' + i, + unit='Ohm', + value=50, + byte_to_value_dict={2: 50}) + + self.add_parameter(name='trigger_operation', + parameter_class=AlazarParameter, + label='Trigger Operation', + unit=None, + value='TRIG_ENGINE_OP_J', + byte_to_value_dict={ + 0: 'TRIG_ENGINE_OP_J', + 1: 'TRIG_ENGINE_OP_K', + 2: 'TRIG_ENGINE_OP_J_OR_K', + 3: 'TRIG_ENGINE_OP_J_AND_K', + 4: 'TRIG_ENGINE_OP_J_XOR_K', + 5: 'TRIG_ENGINE_OP_J_AND_NOT_K', + 6: 'TRIG_ENGINE_OP_NOT_J_AND_K'}) + for i in ['1', '2']: + self.add_parameter(name='trigger_engine' + i, + parameter_class=AlazarParameter, + label='Trigger Engine ' + i, + unit=None, + value='TRIG_ENGINE_' + ('J' if i == 0 else 'K'), + byte_to_value_dict={0: 'TRIG_ENGINE_J', + 1: 'TRIG_ENGINE_K'}) + self.add_parameter(name='trigger_source' + i, + parameter_class=AlazarParameter, + label='Trigger Source ' + i, + unit=None, + value='EXTERNAL', + byte_to_value_dict={0: 'CHANNEL_A', + 1: 'CHANNEL_B', + 2: 'EXTERNAL', + 3: 'DISABLE'}) + self.add_parameter(name='trigger_slope' + i, + parameter_class=AlazarParameter, + label='Trigger Slope ' + i, + unit=None, + value='TRIG_SLOPE_POSITIVE', + byte_to_value_dict={1: 'TRIG_SLOPE_POSITIVE', + 2: 'TRIG_SLOPE_NEGATIVE'}) + self.add_parameter(name='trigger_level' + i, + parameter_class=AlazarParameter, + label='Trigger Level ' + i, + unit=None, + value=140, + vals=validators.Ints(0, 255)) + + self.add_parameter(name='external_trigger_coupling', + parameter_class=AlazarParameter, + label='External Trigger Coupling', + unit=None, + value='DC', + byte_to_value_dict={1: 'AC', 2: 'DC'}) + self.add_parameter(name='external_trigger_range', + parameter_class=AlazarParameter, + label='External Trigger Range', + unit=None, + value='ETR_2V5', + byte_to_value_dict={0: 'ETR_5V', 1: 'ETR_1V', + 2: 'ETR_TTL', 3: 'ETR_2V5'}) + self.add_parameter(name='trigger_delay', + parameter_class=AlazarParameter, + label='Trigger Delay', + unit='Sample clock cycles', + value=0, + vals=validators.Ints(min_value=0)) + + # NOTE: The board will wait for a for this amount of time for a + # trigger event. If a trigger event does not arrive, then the + # board will automatically trigger. Set the trigger timeout value + # to 0 to force the board to wait forever for a trigger event. + # + # IMPORTANT: The trigger timeout value should be set to zero after + # appropriate trigger parameters have been determined, otherwise + # the board may trigger if the timeout interval expires before a + # hardware trigger event arrives. + self.add_parameter(name='timeout_ticks', + parameter_class=AlazarParameter, + label='Timeout Ticks', + unit='10 us', + value=0, + vals=validators.Ints(min_value=0)) + + self.add_parameter(name='aux_io_mode', + parameter_class=AlazarParameter, + label='AUX I/O Mode', + unit=None, + value='AUX_IN_AUXILIARY', + byte_to_value_dict={0: 'AUX_OUT_TRIGGER', + 1: 'AUX_IN_TRIGGER_ENABLE', + 13: 'AUX_IN_AUXILIARY'}) + + self.add_parameter(name='aux_io_param', + parameter_class=AlazarParameter, + unit=None, + value='NONE', + byte_to_value_dict={0: 'NONE', + 1: 'TRIG_SLOPE_POSITIVE', + 2: 'TRIG_SLOPE_NEGATIVE'}) + + # ----- Parameters for the acquire function ----- + self.add_parameter(name='mode', + parameter_class=AlazarParameter, + label='Acquisition mode', + unit=None, + value='NPT', + byte_to_value_dict={0x200: 'NPT', 0x400: 'TS'}) + self.add_parameter(name='samples_per_record', + parameter_class=AlazarParameter, + label='Samples per Record', + unit=None, + value=1024, + vals=validators.Multiples( + divisor=self.samples_divisor, min_value=256)) + self.add_parameter(name='records_per_buffer', + parameter_class=AlazarParameter, + label='Records per Buffer', + unit=None, + value=10, + vals=validators.Ints(min_value=0)) + self.add_parameter(name='buffers_per_acquisition', + parameter_class=AlazarParameter, + label='Buffers per Acquisition', + unit=None, + value=10, + vals=validators.Ints(min_value=0)) + self.add_parameter(name='channel_selection', + parameter_class=AlazarParameter, + label='Channel Selection', + unit=None, + value='AB', + byte_to_value_dict={1: 'A', 2: 'B', 3: 'AB'}) + self.add_parameter(name='transfer_offset', + parameter_class=AlazarParameter, + label='Transfer Offset', + unit='Samples', + value=0, + vals=validators.Ints(min_value=0)) + self.add_parameter(name='external_startcapture', + parameter_class=AlazarParameter, + label='External Startcapture', + unit=None, + value='ENABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x1: 'ENABLED'}) + self.add_parameter(name='enable_record_headers', + parameter_class=AlazarParameter, + label='Enable Record Headers', + unit=None, + value='DISABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x8: 'ENABLED'}) + self.add_parameter(name='alloc_buffers', + parameter_class=AlazarParameter, + label='Alloc Buffers', + unit=None, + value='DISABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x20: 'ENABLED'}) + self.add_parameter(name='fifo_only_streaming', + parameter_class=AlazarParameter, + label='Fifo Only Streaming', + unit=None, + value='DISABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x800: 'ENABLED'}) + self.add_parameter(name='interleave_samples', + parameter_class=AlazarParameter, + label='Interleave Samples', + unit=None, + value='DISABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x1000: 'ENABLED'}) + self.add_parameter(name='get_processed_data', + parameter_class=AlazarParameter, + label='Get Processed Data', + unit=None, + value='DISABLED', + byte_to_value_dict={0x0: 'DISABLED', + 0x2000: 'ENABLED'}) + self.add_parameter(name='allocated_buffers', + parameter_class=AlazarParameter, + label='Allocated Buffers', + unit=None, + value=4, + vals=validators.Ints(min_value=0)) + + self.add_parameter(name='buffer_timeout', + parameter_class=AlazarParameter, + label='Buffer Timeout', + unit='ms', + value=1000, + vals=validators.Ints(min_value=0)) + + self.add_parameter(name='effective_sample_rate', + unit='Hz', + alternative='decimation and sample_rate or external_sample_rate depending on clock_source', + parameter_class=EffectiveSampleRateParameter) + + model = self.get_idn()['model'] + if model != 'ATS9360': + raise Exception("The Alazar board kind is not 'ATS9360'," + " found '" + str(model) + "' instead.") + + + def get_sample_rate(self): + """ + Obtain the effective sampling rate of the acquisition + based on clock type, clock speed and decimation + + Returns: + the number of samples (per channel) per second + """ + if self.clock_source.get() == 'EXTERNAL_CLOCK_10MHz_REF': + rate = self.external_sample_rate.get() + elif self.clock_source.get() == 'INTERNAL_CLOCK': + rate = self.sample_rate.get() + else: + raise Exception("Don't know how to get sample rate with {}".format(self.clock_source.get())) + + if rate == '1GHz_REFERENCE_CLOCK': + rate = 1e9 + + decimation = self.decimation.get() + if decimation > 0: + return rate / decimation + else: + return rate diff --git a/qcodes/instrument_drivers/AlazarTech/ATS9870.py b/qcodes/instrument_drivers/AlazarTech/ATS9870.py index eebda198549..ba9c0acb307 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS9870.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS9870.py @@ -7,7 +7,7 @@ class AlazarTech_ATS9870(AlazarTech_ATS): This class is the driver for the ATS9870 board it inherits from the ATS base class - it creates all necessary parameters for the Alazar card + It creates all necessary parameters for the Alazar card """ def __init__(self, name, **kwargs): dll_path = 'C:\\WINDOWS\\System32\\ATSApi.dll' @@ -150,6 +150,23 @@ def __init__(self, name, **kwargs): unit='10 us', value=0, vals=validators.Ints(min_value=0)) + self.add_parameter(name='aux_io_mode', + parameter_class=AlazarParameter, + label='AUX I/O Mode', + unit=None, + value='AUX_IN_AUXILIARY', + byte_to_value_dict={0: 'AUX_OUT_TRIGGER', + 1: 'AUX_IN_TRIGGER_ENABLE', + 13: 'AUX_IN_AUXILIARY'}) + + self.add_parameter(name='aux_io_param', + parameter_class=AlazarParameter, + unit=None, + value='NONE', + byte_to_value_dict={0: 'NONE', + 1: 'TRIG_SLOPE_POSITIVE', + 2: 'TRIG_SLOPE_NEGATIVE'}) + # ----- Parameters for the acquire function ----- self.add_parameter(name='mode', diff --git a/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py b/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py index a90269a8503..e99470a375a 100644 --- a/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +++ b/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py @@ -27,8 +27,10 @@ def __init__(self, name, alazar_name, demodulation_frequency, **kwargs): self.demodulation_frequency = demodulation_frequency self.acquisitionkwargs = {} self.samples_per_record = None + self.bits_per_sample = None self.records_per_buffer = None self.buffers_per_acquisition = None + self.allocated_buffers = None # TODO(damazter) (S) this is not very general: self.number_of_channels = 2 self.cos_list = None @@ -43,8 +45,9 @@ def update_acquisitionkwargs(self, **kwargs): """ This method must be used to update the kwargs used for the acquisition with the alazar_driver.acquire - :param kwargs: - :return: + + Args: + **kwargs: New kwargs for the acquisition """ self.acquisitionkwargs.update(**kwargs) @@ -52,7 +55,6 @@ def do_acquisition(self): """ this method performs an acquisition, which is the get_cmd for the acquisiion parameter of this instrument - :return: """ value = self._get_alazar().acquire(acquisition_controller=self, **self.acquisitionkwargs) @@ -61,7 +63,6 @@ def do_acquisition(self): def pre_start_capture(self): """ See AcquisitionController - :return: """ alazar = self._get_alazar() self.samples_per_record = alazar.samples_per_record.get() @@ -81,7 +82,6 @@ def pre_start_capture(self): def pre_acquire(self): """ See AcquisitionController - :return: """ # this could be used to start an Arbitrary Waveform Generator, etc... # using this method ensures that the contents are executed AFTER the @@ -91,14 +91,12 @@ def pre_acquire(self): def handle_buffer(self, data): """ See AcquisitionController - :return: """ self.buffer += data def post_acquire(self): """ See AcquisitionController - :return: """ alazar = self._get_alazar() # average all records in a buffer diff --git a/qcodes/instrument_drivers/AlazarTech/acq_controllers/ATS9360Controller.py b/qcodes/instrument_drivers/AlazarTech/acq_controllers/ATS9360Controller.py new file mode 100644 index 00000000000..2307565b452 --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/acq_controllers/ATS9360Controller.py @@ -0,0 +1,463 @@ +import logging +from ..ATS import AcquisitionController +import numpy as np +import qcodes.instrument_drivers.AlazarTech.acq_helpers as helpers +from ..acquisition_parameters import AcqVariablesParam, \ + ExpandingAlazarArrayMultiParameter, \ + NonSettableDerivedParameter, \ + DemodFreqParameter + + +class ATS9360Controller(AcquisitionController): + """ + This is the Acquisition Controller class which works with the ATS9360, + averaging over records and buffers and demodulating with software reference + signal(s). It may optionally integrate over the samples following the post processing + + + Args: + name: name for this acquisition_controller as an instrument + alazar_name: name of the alazar instrument such that this + controller can communicate with the Alazar + filter (default 'win'): filter to be used to filter out double freq + component ('win' - window, 'ls' - least squared, 'ave' - averaging) + numtaps (default 101): number of freq components used in filter + chan_b (default False): whether there is also a second channel of data + to be processed and returned. Not currently fully implemented. + **kwargs: kwargs are forwarded to the Instrument base class + + TODO(nataliejpg) test filter options + TODO(JHN) Use filtfit for better performance? + TODO(JHN) Test demod+filtering and make it more modular + TODO(nataliejpg) finish implementation of channel b option + TODO(JHN) Option to not read channel b at all (Speedup) + TODO(nataliejpg) what should be private? + TODO(nataliejpg) where should filter_dict live? + """ + + filter_dict = {'win': 0, 'ls': 1, 'ave': 2} + + def __init__(self, name, alazar_name, filter: str = 'win', + numtaps: int =101, chan_b: bool = False, + integrate_samples: bool = False, + average_records: bool = True, + **kwargs): + self.filter_settings = {'filter': self.filter_dict[filter], + 'numtaps': numtaps} + self.chan_b = chan_b + self.number_of_channels = 2 + if not integrate_samples and not average_records: + raise RuntimeError("You need to either average records or integrate over samples") + + super().__init__(name, alazar_name, **kwargs) + + self.add_parameter(name='acquisition', + integrate_samples=integrate_samples, + average_records=average_records, + parameter_class=ExpandingAlazarArrayMultiParameter) + + self._integrate_samples = integrate_samples + + self.add_parameter(name='int_time', + check_and_update_fn=self._update_int_time, + default_fn=self._int_time_default, + parameter_class=AcqVariablesParam) + self.add_parameter(name='int_delay', + check_and_update_fn=self._update_int_delay, + default_fn=self._int_delay_default, + parameter_class=AcqVariablesParam) + self.add_parameter(name='num_avg', + check_and_update_fn=self._update_num_avg, + default_fn= lambda : 1, + parameter_class=AcqVariablesParam) + self.add_parameter(name='allocated_buffers', + alternative='not controllable in this controller', + parameter_class=NonSettableDerivedParameter) + self.add_parameter(name='buffers_per_acquisition', + alternative='not controllable in this controller', + parameter_class=NonSettableDerivedParameter) + if average_records: + self.add_parameter(name='records_per_buffer', + alternative='num_avg', + parameter_class=NonSettableDerivedParameter) + else: + self.add_parameter(name='records_per_buffer', + parameter_class=AcqVariablesParam, + default_fn= lambda : 1, + check_and_update_fn=self._update_records_per_buffer) + self.add_parameter(name='samples_per_record', + alternative='int_time and int_delay', + parameter_class=NonSettableDerivedParameter) + + self.add_parameter(name='demod_freqs', + shape=(), + parameter_class=DemodFreqParameter) + + self.samples_divisor = self._get_alazar().samples_divisor + + + def _update_int_time(self, value, **kwargs): + """ + Function to validate value for int_time before setting parameter + value and update instr attributes. + + Args: + value to be validated and used for instrument attribute update + + Checks: + 0 <= value <= 0.1 seconds + number of oscillation measured in this time + oversampling rate + + Sets: + samples_per_record of acq controller to match int_time and int_delay + """ + if (value is None) or not (0 <= value <= 0.1): + raise ValueError('int_time must be 0 <= value <= 1') + + alazar = self._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + max_demod_freq = self.demod_freqs.get_max_demod_freq() + if max_demod_freq is not None: + self.demod_freqs._verify_demod_freq(max_demod_freq) + if self.int_delay() is None: + self.int_delay.to_default() + int_delay = self.int_delay.get() + self._update_samples_per_record(sample_rate, value, int_delay) + + def _update_samples_per_record(self, sample_rate, int_time, int_delay): + """ + Keeps non settable samples_per_record up to date with int_time int_delay + and updates setpoints as needed. + """ + total_time = (int_time or 0) + (int_delay or 0) + samples_needed = total_time * sample_rate + samples_per_record = helpers.roundup( + samples_needed, self.samples_divisor) + self.samples_per_record._save_val(samples_per_record) + self.acquisition.set_setpoints_and_labels() + + def _update_records_per_buffer(self, value, **kwargs): + # Hack store the value early so its useful for set_setpoints... + self.records_per_buffer._save_val(value) + self.acquisition.set_setpoints_and_labels() + + def _update_int_delay(self, value, **kwargs): + """ + Function to validate value for int_delay before setting parameter + value and update instr attributes. + + Args: + value to be validated and used for instrument attribute update + + Checks: + 0 <= value <= 0.1 seconds + number of samples discarded >= numtaps + + Sets: + samples_per_record of acq controller to match int_time and int_delay + samples_per_record of acq controller + acquisition_kwarg['samples_per_record'] of acquisition param + setpoints of acquisition param + """ + int_delay_min = 0 + int_delay_max = 0.1 + if (value is None) or not (int_delay_min <= value <= int_delay_max): + raise ValueError('int_delay must be {} <= value <= {}. Got {}'.format(int_delay_min, + int_delay_max, + value)) + alazar = self._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + samples_delay_min = (self.filter_settings['numtaps'] - 1) + int_delay_min = samples_delay_min / sample_rate + if value < int_delay_min: + logging.warning( + 'delay is less than recommended for filter choice: ' + '(expect delay >= {})'.format(int_delay_min)) + + int_time = self.int_time.get() + self._update_samples_per_record(sample_rate, int_time, value) + + def _update_num_avg(self, value: int, **kwargs): + if not isinstance(value, int) or value < 1: + raise ValueError('number of averages must be a positive integer') + + + if self.acquisition._average_records: + self.records_per_buffer._save_val(value) + self.buffers_per_acquisition._save_val(1) + self.allocated_buffers._save_val(1) + else: + self.buffers_per_acquisition._save_val(value) + self.allocated_buffers._save_val(2) + + def _int_delay_default(self): + """ + Function to generate default int_delay value + + Returns: + minimum int_delay recommended for (numtaps - 1) + samples to be discarded as recommended for filter + """ + alazar = self._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + samp_delay = self.filter_settings['numtaps'] - 1 + return samp_delay / sample_rate + + def _int_time_default(self): + """ + Function to generate default int_time value + + Returns: + max total time for integration based on samples_per_record, + sample_rate and int_delay + """ + samples_per_record = self.samples_per_record.get() + if samples_per_record in (0 or None): + raise ValueError('Cannot set int_time to max if acq controller' + ' has 0 or None samples_per_record, choose a ' + 'value for int_time and samples_per_record will ' + 'be set accordingly') + alazar = self._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + total_time = ((samples_per_record / sample_rate) - + (self.int_delay() or 0)) + return total_time + + def update_filter_settings(self, filter, numtaps): + """ + Updates the settings of the filter for filtering out + double frequency component for demodulation. + + Args: + filter (str): filter type ('win' or 'ls') + numtaps (int): numtaps for filter + """ + self.filter_settings.update({'filter': self.filter_dict[filter], + 'numtaps': numtaps}) + + def update_acquisition_kwargs(self, **kwargs): + """ + Updates the kwargs to be used when + alazar_driver.acquisition() is called via a get call of the + acquisition parameter. Should be used by the user for + updating averaging settings. If integrating over samples + 'samples_per_record' cannot be set directly it should instead + be set via the int_time and int_delay parameters. + + Kwargs (ints): + - records_per_buffer + - buffers_per_acquisition + - allocated_buffers + """ + if 'samples_per_record' in kwargs: + raise ValueError('Samples_per_record should not be set manually ' + 'via update_acquisition_kwargs and should instead' + ' be set by setting int_time and int_delay') + self.acquisition.acquisition_kwargs.update(**kwargs) + self.acquisition.set_setpoints_and_labels() + + def pre_start_capture(self): + """ + Called before capture start to update Acquisition Controller with + Alazar acquisition params and set up software wave for demodulation. + """ + alazar = self._get_alazar() + acq_s_p_r = self.samples_per_record.get() + inst_s_p_r = alazar.samples_per_record.get() + sample_rate = alazar.effective_sample_rate.get() + if acq_s_p_r != inst_s_p_r: + raise Exception('acq controller samples per record {} does not match' + ' instrument value {}, most likely need ' + 'to set and check int_time and int_delay'.format(acq_s_p_r, inst_s_p_r)) + # if acq_s_r != inst_s_r: + # raise Exception('acq controller sample rate {} does not match ' + # 'instrument value {}, most likely need ' + # 'to set and check int_time and int_delay'.format(acq_s_r, inst_s_r)) + samples_per_record = inst_s_p_r + demod_freqs = self.demod_freqs.get() + # if len(demod_freqs) == 0: + # raise Exception('no demod_freqs set') + + records_per_buffer = alazar.records_per_buffer.get() + max_samples = self._get_alazar().get_idn()['max_samples'] + samples_per_buffer = records_per_buffer * samples_per_record + if samples_per_buffer > max_samples: + raise RuntimeError("Trying to acquire {} samples in one buffer maximum supported is {}".format(samples_per_buffer, max_samples)) + + self.board_info = alazar.get_idn() + self.buffer = np.zeros(samples_per_record * + records_per_buffer * + self.number_of_channels) + avg_buffers = True + if avg_buffers: + len_buffers = 1 + else: + pass # not implemented yet + + if self.acquisition._average_records: + len_records = 1 + else: + len_records = records_per_buffer + + num_demods = self.demod_freqs.get_num_demods() + if num_demods: + mat_shape = (num_demods, len_buffers, + len_records, self.samples_per_record.get()) + self.mat_shape = mat_shape + integer_list = np.arange(samples_per_record) + integer_mat = (np.outer(np.ones(len_buffers), + np.outer(np.ones(len_records), integer_list))) + angle_mat = 2 * np.pi * \ + np.outer(demod_freqs, integer_mat).reshape(mat_shape) / sample_rate + self.cos_mat = np.cos(angle_mat) + self.sin_mat = np.sin(angle_mat) + + def pre_acquire(self): + pass + + def handle_buffer(self, data): + """ + Adds data from Alazar to buffer (effectively averaging) + """ + self.buffer += data + + def post_acquire(self): + """ + Processes the data according to ATS9360 settings, splitting into + records and averaging over them, then applying demodulation fit + nb: currently only channel A. Depending on the value of integrate_samples + it may either sum over all samples or return arrays of individual samples + for all the data given below. + + Returns: + - Raw data + - For each demodulation frequency: + * magnitude + * phase + """ + + # for ATS9360 samples are arranged in the buffer as follows: + # S00A, S00B, S01A, S01B...S10A, S10B, S11A, S11B... + # where SXYZ is record X, sample Y, channel Z. + + # break buffer up into records and averages over them + alazar = self._get_alazar() + samples_per_record = alazar.samples_per_record.get() + records_per_buffer = alazar.records_per_buffer.get() + buffers_per_acquisition = alazar.buffers_per_acquisition.get() + number_of_buffers = 1 # Hardcoded for now assuming all buffers go to the same array + reshaped_buf = self.buffer.reshape(number_of_buffers, + records_per_buffer, + samples_per_record, + self.number_of_channels) + + channelAData = reshaped_buf[:, :, :, 0] + if self.acquisition._average_records: + recordA = np.uint16(np.mean(channelAData, axis=1, keepdims=True) / + buffers_per_acquisition) + else: + recordA = np.uint16(channelAData/buffers_per_acquisition) + + recordA = self._to_volts(recordA) + + # do demodulation + if self.demod_freqs.get_num_demods(): + magA, phaseA = self._fit(recordA) + if self._integrate_samples: + magA = np.mean(magA, axis=-1) + phaseA = np.mean(phaseA, axis=-1) + + unpacked = [] + if self._integrate_samples: + unpacked.append(np.mean(recordA, axis=-1)) + else: + unpacked.append(np.squeeze(recordA)) + + if self.demod_freqs.get_num_demods(): + for i in range(magA.shape[0]): + unpacked.append(magA[i]) + unpacked.append(phaseA[i]) + # same for chan b + if self.chan_b: + raise NotImplementedError('chan b code not complete') + + + return tuple(unpacked) + + def _to_volts(self, record): + # convert rec to volts + bps = self.board_info['bits_per_sample'] + if bps == 12: + volt_rec = helpers.sample_to_volt_u12(record, bps) + else: + logging.warning('sample to volt conversion does not exist for' + ' bps != 12, centered raw samples returned') + volt_rec = record - np.mean(record) + return volt_rec + + def _fit(self, volt_rec): + """ + Applies low bandpass filter and demodulation fit, + and integration limits to samples array + + Args: + record (numpy array): record from alazar to be multiplied + with the software signal, filtered and limited + to ifantegration limits shape = (samples_taken, ) + + Returns: + magnitude (numpy array): shape = (demod_length, samples_after_limiting) + phase (numpy array): shape = (demod_length, samples_after_limiting) + """ + + # volt_rec to matrix and multiply with demodulation signal matrices + alazar = self._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + demod_length = self.demod_freqs.get_num_demods() + volt_rec_mat = np.outer(np.ones(demod_length), volt_rec).reshape(self.mat_shape) + re_mat = np.multiply(volt_rec_mat, self.cos_mat) + im_mat = np.multiply(volt_rec_mat, self.sin_mat) + + # filter out higher freq component + cutoff = self.demod_freqs.get_max_demod_freq() / 10 + if self.filter_settings['filter'] == 0: + re_filtered = helpers.filter_win(re_mat, cutoff, + sample_rate, + self.filter_settings['numtaps'], + axis=-1) + im_filtered = helpers.filter_win(im_mat, cutoff, + sample_rate, + self.filter_settings['numtaps'], + axis=-1) + elif self.filter_settings['filter'] == 1: + re_filtered = helpers.filter_ls(re_mat, cutoff, + sample_rate, + self.filter_settings['numtaps'], + axis=-1) + im_filtered = helpers.filter_ls(im_mat, cutoff, + sample_rate, + self.filter_settings['numtaps'], + axis=-1) + elif self.filter_settings['filter'] == 2: + re_filtered = re_mat + im_filtered = im_mat + + if self._integrate_samples: + # apply integration limits + beginning = int(self.int_delay() * sample_rate) + end = beginning + int(self.int_time() * sample_rate) + + re_limited = re_filtered[..., beginning:end] + im_limited = im_filtered[..., beginning:end] + else: + re_limited = re_filtered + im_limited = im_filtered + + # convert to magnitude and phase + complex_mat = re_limited + im_limited * 1j + magnitude = abs(complex_mat) + phase = np.angle(complex_mat, deg=True) + + return magnitude, phase + diff --git a/qcodes/instrument_drivers/AlazarTech/acq_controllers/__init__.py b/qcodes/instrument_drivers/AlazarTech/acq_controllers/__init__.py new file mode 100644 index 00000000000..c822410d32c --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/acq_controllers/__init__.py @@ -0,0 +1 @@ +from .ATS9360Controller import ATS9360Controller diff --git a/qcodes/instrument_drivers/AlazarTech/acq_helpers.py b/qcodes/instrument_drivers/AlazarTech/acq_helpers.py new file mode 100644 index 00000000000..791b3de17b1 --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/acq_helpers.py @@ -0,0 +1,76 @@ +import numpy as np +from scipy import signal + + +def filter_win(rec, cutoff, sample_rate, numtaps, axis=-1): + """ + low pass filter, returns filtered signal using FIR window + filter + + Args: + rec: record to filter + cutoff: cutoff frequency + sample_rate: sampling rate + numtaps: number of frequency comppnents to use in the filer + axis: axis of record to apply filter along + """ + nyq_rate = sample_rate / 2. + fir_coef = signal.firwin(numtaps, cutoff / nyq_rate) + filtered_rec = signal.lfilter(fir_coef, 1.0, rec, axis=axis) + return filtered_rec + + +def filter_ls(rec, cutoff, sample_rate, numtaps, axis=-1): + """ + low pass filter, returns filtered signal using FIR + least squared filter + + Args: + rec: record to filter + cufoff: cutoff frequency + sample_rate: sampling rate + numtaps: number of frequency comppnents to use in the filer + axis: axis of record to apply filter along + """ + raise NotImplementedError + + +def sample_to_volt_u12(raw_samples, bps): + """ + Applies volts conversion for 12 bit sample data stored + in 2 bytes + + return: + samples_magnitude_array + samples_phase_array + """ + + # right_shift 16-bit sample by 4 to get 12 bit sample + shifted_samples = np.right_shift(raw_samples, 4) + + # Alazar calibration + code_zero = (1 << (bps - 1)) - 0.5 + code_range = (1 << (bps - 1)) - 0.5 + + # TODO(nataliejpg) make this not hard coded + input_range_volts = 1 + # Convert to volts + volt_samples = np.float64(input_range_volts * + (shifted_samples - code_zero) / code_range) + + return volt_samples + + +def roundup(num, to_nearest): + """ + Rounds up the 'num' to the nearest multiple of 'to_nearest', all int + + Args: + num: to be rounded up + to_nearest: value to be rounded to int multiple of + return: + rounded up value + + """ + remainder = num % to_nearest + return int(num if remainder == 0 else num + to_nearest - remainder) diff --git a/qcodes/instrument_drivers/AlazarTech/acquisition_parameters.py b/qcodes/instrument_drivers/AlazarTech/acquisition_parameters.py new file mode 100644 index 00000000000..73835333378 --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/acquisition_parameters.py @@ -0,0 +1,492 @@ +from qcodes import Parameter, MultiParameter, ArrayParameter +import numpy as np +import logging + +class AcqVariablesParam(Parameter): + """ + Parameter of an AcquisitionController which has a _check_and_update_instr + function used for validation and to update instrument attributes and a + _get_default function which it uses to set the AcqVariablesParam to an + instrument calculated default. + + Args: + name: name for this parameter + instrument: acquisition controller instrument this parameter belongs to + check_and_update_fn: instrument function to be used for value + validation and updating instrument values + default_fn (optional): instrument function to be used to calculate + a default value to set parameter to + initial_value (optional): initial value for parameter + """ + + def __init__(self, name, instrument, check_and_update_fn, + default_fn=None, initial_value=None): + super().__init__(name) + self._instrument = instrument + self._save_val(initial_value) + self._check_and_update_instr = check_and_update_fn + if default_fn is not None: + self._get_default = default_fn + + def set(self, value): + """ + Function which checks value using validation function and then sets + the Parameter value to this value. + + Args: + value: value to set the parameter to + """ + self._check_and_update_instr(value, param_name=self.name) + self._save_val(value) + + def get(self): + return self._latest()['value'] + + def to_default(self): + """ + Function which executes the default_fn specified to calculate the + default value based on instrument values and then calls the set + function with this value + """ + try: + default = self._get_default() + except AttributeError as e: + raise AttributeError('no default function for {} Parameter ' + '{}'.format(self.name, e)) + self.set(default) + + def check(self): + """ + Function which checks the current Parameter value using the specified + check_and_update_fn which can also serve to update instrument values. + + Return: + True (if no errors raised when check_and_update_fn executed) + """ + val = self._latest()['value'] + self._check_and_update_instr(val, param_name=self.name) + return True + + +class AlazarMultiArray(MultiParameter): + """ + Hardware controlled parameter class for Alazar acquisition. To be used with + Acquisition Controller (tested with ATS9360 board) + + Alazar Instrument 'acquire' returns a buffer of data each time a buffer is + filled (channels * samples * records) which is processed by the + post_acquire function of the Acquisition Controller and finally the + processed result is returned when the AlazarMultiArray parameter is called. + + Args: + name: name for this parameter + names: names of the two arrays returned from get + instrument: acquisition controller instrument this parameter belongs to + demod_length (int): number of demodulators. Default 1 + """ + + def __init__(self, name, instrument, names=("A", "B"), demod_length=1): + if demod_length > 1: + shapes = ((demod_length, ), (demod_length, )) + else: + shapes = ((), ()) + super().__init__(name, names=names, shapes=shapes, instrument=instrument) + self.acquisition_kwargs = {} + + def update_demod_setpoints(self, demod_freqs): + """ + Function to update the demodulation frequency setpoints to be called + when a demod_freq Parameter of the acq controller is updated + + Args: + demod_freqs: numpy array of demodulation frequencies to use as + setpoints if length > 1 + """ + demod_length = self._instrument._demod_length + if demod_length > 1: + pass + # self.setpoints = ((demod_freqs, ), (demod_freqs, )) + else: + pass + + def get(self): + """ + Gets the output by calling acquire on the alazar (which in turn calls the + processing functions of the acquisition controller before returning the reshaped data. + The exact data will depend on the AcquisitionController used + + returns: + - A a numpy array of Alazar data + - B a numpy array of Alazar data + """ + a, b = self._instrument._get_alazar().acquire( + acquisition_controller=self._instrument, + **self.acquisition_kwargs) + return a, b + + +class AlazarMultiArray2D(AlazarMultiArray): + + def update_sweep(self, start, stop, npts): + """ + Function which updates the shape of the parameter (and it's setpoints + when this is fixed) + + Args: + start: start time of samples returned after processing + stop: stop time of samples returned after processing + npts: number of samples returned after processing + """ + demod_length = self._instrument._demod_length + # self._time_list = tuple(np.linspace(start, stop, num=npts) + if demod_length > 1: + # demod_freqs = self._instrument.get_demod_freqs() + # self.setpoints = ((demod_freqs, self._time_list), + # (demod_freqs, self._time_list)) + self.shapes = ((demod_length, npts), (demod_length, npts)) + else: + self.shapes = ((npts,), (npts,)) + # self.setpoints = ((self._time_list,), (self._time_list,)) + + +class AlazarMultiArray3D(AlazarMultiArray): + + def update_buf_sweep(self, buf_npts, buf_start=None, buf_stop=None): + """ + Function which updates the shape of the parameter (and it's setpoints + when this is fixed) + + Args: + buf_npts: number of buffers returned + buf_start (optional): start value of buffers returned + buf_stop (optional): stop value of records returned + """ + demod_length = self._instrument._demod_length + # self._buf_list = tuple(np.linspace(buf_start, + # buf_stop, num=buf_npts)) + self._buf_npts = buf_npts + if demod_length > 1: + # demod_freqs = self._instrument.get_demod_freqs() + # self.setpoints = ((demod_freqs, self._buf_list, self._rec_list), + # (demod_freqs, self._buf_list, self._rec_list)) + self.shapes = ((demod_length, self._buf_npts, self._rec_npts), + (demod_length, self._buf_npts, self._rec_npts)) + else: + self.shapes = ((self._buf_npts, self._rec_npts), + (self._buf_npts, self._rec_npts)) + # self.setpoints = ((self._buf_list, self._rec_list), + # (self._buf_list, self._rec_list)) + + def update_rec_sweep(self, rec_npts, rec_start=None, rec_stop=None): + """ + Function which updates the shape of the parameter (and it's setpoints + when this is fixed) + + Args: + rec_npts: number of records returned after processing + rec_start (optional): start value of records returned + rec_stop (optional): stop value of records returned + """ + demod_length = self._instrument._demod_length + # self._rec_list = tuple(np.linspace(rec_start, + # rec_stop, num=rec_npts)) + self._rec_npts = rec_npts + if demod_length > 1: + # demod_freqs = self._instrument.get_demod_freqs() + # self.setpoints = ((demod_freqs, self._buf_list, self._rec_list), + # (demod_freqs, self._buf_list, self._rec_list)) + self.shapes = ((demod_length, self._buf_npts, self._rec_npts), + (demod_length, self._buf_npts, self._rec_npts)) + else: + self.shapes = ((self._buf_npts, self._rec_npts), + (self._buf_npts, self._rec_npts)) + # self.setpoints = ((self._buf_list, self._rec_list), + # (self._buf_list, self._rec_list)) + + +class ExpandingAlazarArrayMultiParameter(MultiParameter): + def __init__(self, + name, + instrument, + names = ('raw_output',), + labels = ("raw output",), + units = ('v',), + shapes = ((1,),), + setpoints = (((1,),),), + setpoint_names = None, + setpoint_labels = None, + setpoint_units = None, + integrate_samples=False, + average_records=True): + self.acquisition_kwargs = {} + self._integrate_samples = integrate_samples + self._average_records = average_records + + if setpoint_names: + self.setpoint_names_base = setpoint_names[0] + elif integrate_samples and average_records: + self.setpoint_names_base = () + elif integrate_samples: + self.setpoint_names_base = ('record_num',) + elif average_records: + self.setpoint_names_base = ('time',) + if setpoint_labels: + self.setpoint_labels_base = setpoint_names[0] + elif integrate_samples and average_records: + self.setpoint_labels_base = () + elif integrate_samples: + self.setpoint_labels_base = ('record num',) + elif average_records: + self.setpoint_labels_base = ('time',) + if setpoint_units: + self.setpoint_units_base = setpoint_units[0] + elif integrate_samples and average_records: + self.setpoint_units_base = () + elif integrate_samples: + self.setpoint_units_base = ('',) + elif average_records: + self.setpoint_units_base = ('s',) + + self.setpoints_start = 0 + self.setpoints_stop = 0 + + super().__init__(name, + names=names, + units=units, + labels=labels, + shapes=shapes, + instrument=instrument, + setpoints=setpoints, + setpoint_names=setpoint_names, + setpoint_labels=setpoint_labels, + setpoint_units=setpoint_units) + + + + def set_base_setpoints(self, base_name=None, base_label=None, base_unit=None, + setpoints_start=None, setpoints_stop=None): + if base_name is not None: + self.setpoint_names_base = (base_name,) + if base_label is not None: + self.setpoint_labels_base = (base_label,) + if base_unit is not None: + self.setpoint_units_base = (base_unit,) + if setpoints_start is not None: + self.setpoints_start = setpoints_start + if setpoints_stop is not None: + self.setpoints_stop = setpoints_stop + self.set_setpoints_and_labels() + + def set_setpoints_and_labels(self): + if not self._integrate_samples: + int_time = self._instrument.int_time.get() or 0 + int_delay = self._instrument.int_delay.get() or 0 + total_time = int_time + int_delay + samples = self._instrument.samples_per_record.get() + if total_time and samples: + start = 0 + stop = total_time + else: + start = 0 + stop = 1 + samples = samples or 1 + arraysetpoints = (tuple(np.linspace(start, stop, samples)),) + base_shape = (len(arraysetpoints[0]),) + elif not self._average_records: + num_records = self._instrument.records_per_buffer.get() or 0 + start = self.setpoints_start + stop = self.setpoints_stop or num_records-1 + arraysetpoints = (tuple(np.linspace(start, stop, num_records)),) + base_shape = (self._instrument.records_per_buffer.get(),) + else: + arraysetpoints = () + base_shape = () + setpoints = [arraysetpoints] + names = [self.names[0]] + labels = [self.labels[0]] + setpoint_names = [self.setpoint_names_base] + setpoint_labels = [self.setpoint_labels_base] + setpoint_units = [self.setpoint_units_base] + units = [self.units[0]] + shapes = [base_shape] + demod_freqs = self._instrument.demod_freqs.get() + for i, demod_freq in enumerate(demod_freqs): + names.append("demod_freq_{}_mag".format(i)) + labels.append("demod freq {} mag".format(i)) + names.append("demod_freq_{}_phase".format(i)) + labels.append("demod freq {} phase".format(i)) + units.append('v') + units.append('v') + shapes.append(base_shape) + shapes.append(base_shape) + setpoints.append(arraysetpoints) + setpoint_names.append(self.setpoint_names_base) + setpoint_labels.append(self.setpoint_labels_base) + setpoint_units.append(self.setpoint_units_base) + setpoints.append(arraysetpoints) + setpoint_names.append(self.setpoint_names_base) + setpoint_labels.append(self.setpoint_labels_base) + setpoint_units.append(self.setpoint_units_base) + self.names = tuple(names) + self.labels = tuple(labels) + self.units = tuple(units) + self.shapes = tuple(shapes) + self.setpoints = tuple(setpoints) + self.setpoint_names = tuple(setpoint_names) + self.setpoint_labels = tuple(setpoint_labels) + self.setpoint_units = tuple(setpoint_units) + + def get(self): + inst = self._instrument + params_to_kwargs = ['samples_per_record', 'records_per_buffer', + 'buffers_per_acquisition', 'allocated_buffers'] + acq_kwargs = self.acquisition_kwargs.copy() + additional_acq_kwargs = {key: val.get() for key, val in inst.parameters.items() if + key in params_to_kwargs} + acq_kwargs.update(additional_acq_kwargs) + + output = self._instrument._get_alazar().acquire( + acquisition_controller=self._instrument, + **acq_kwargs) + return output + + +class NonSettableDerivedParameter(Parameter): + """ + Parameter of an AcquisitionController which cannot be updated directly + as it's value is derived from other parameters. This is intended to be + used in high level APIs where Alazar parameters such as 'samples_per_record' + are not set directly but are parameters of the actual instrument anyway. + + This assumes that the parameter is stored via a call to '_save_val' by + any set of parameter that this parameter depends on. + + Args: + name: name for this parameter + instrument: acquisition controller instrument this parameter belongs to + alternative (str): name of parameter(s) that controls the value of this + parameter and can be set directly. + """ + + def __init__(self, name, instrument, alternative: str, **kwargs): + self._alternative = alternative + super().__init__(name, instrument=instrument, **kwargs) + + def set(self, value): + """ + It's not possible to directly set this parameter as it's derived from other + parameters. + """ + raise NotImplementedError("Cannot directly set {}. To control this parameter" + "set {}".format(self.name, self._alternative)) + + def get(self): + return self.get_latest() + + +class EffectiveSampleRateParameter(NonSettableDerivedParameter): + + + def get(self): + """ + Obtain the effective sampling rate of the acquisition + based on clock type, clock speed and decimation + + Returns: + the number of samples (per channel) per second + """ + if self._instrument.clock_source.get() == 'EXTERNAL_CLOCK_10MHz_REF': + rate = self._instrument.external_sample_rate.get() + elif self._instrument.clock_source.get() == 'INTERNAL_CLOCK': + rate = self._instrument.sample_rate.get() + else: + raise Exception("Don't know how to get sample rate with {}".format(self._instrument.clock_source.get())) + + if rate == '1GHz_REFERENCE_CLOCK': + rate = 1e9 + + decimation = self._instrument.decimation.get() + if decimation > 0: + rate = rate / decimation + + self._save_val(rate) + return rate + + +class DemodFreqParameter(ArrayParameter): + + # + def __init__(self, name, shape, **kwargs): + self._demod_freqs = [] + super().__init__(name, shape, **kwargs) + + + def add_demodulator(self, demod_freq): + ndemod_freqs = len(self._demod_freqs) + if demod_freq not in self._demod_freqs: + self._verify_demod_freq(demod_freq) + self._demod_freqs.append(demod_freq) + self._save_val(self._demod_freqs) + self.shape = (ndemod_freqs+1,) + self._instrument.acquisition.set_setpoints_and_labels() + + def remove_demodulator(self, demod_freq): + ndemod_freqs = len(self._demod_freqs) + if demod_freq in self._demod_freqs: + self._demod_freqs.pop(self._demod_freqs.index(demod_freq)) + self.shape = (ndemod_freqs - 1,) + self._save_val(self._demod_freqs) + self._instrument.acquisition.set_setpoints_and_labels() + + def get(self): + return self._demod_freqs + + def get_num_demods(self): + return len(self._demod_freqs) + + def get_max_demod_freq(self): + if len(self._demod_freqs): + return max(self._demod_freqs) + else: + return None + + def _verify_demod_freq(self, value): + """ + Function to validate a demodulation frequency + + Checks: + - 1e6 <= value <= 500e6 + - number of oscillation measured using current 'int_time' param value + at this demodulation frequency value + - oversampling rate for this demodulation frequency + + Args: + value: proposed demodulation frequency + Returns: + bool: Returns True if suitable number of oscillations are measured and + oversampling is > 1, False otherwise. + Raises: + ValueError: If value is not a valid demodulation frequency. + """ + if (value is None) or not (1e6 <= value <= 500e6): + raise ValueError('demod_freqs must be 1e6 <= value <= 500e6') + isValid = True + alazar = self._instrument._get_alazar() + sample_rate = alazar.effective_sample_rate.get() + int_time = self._instrument.int_time.get() + min_oscillations_measured = int_time * value + oversampling = sample_rate / (2 * value) + if min_oscillations_measured < 10: + isValid = False + logging.warning('{} oscillation measured for largest ' + 'demod freq, recommend at least 10: ' + 'decrease sampling rate, take ' + 'more samples or increase demodulation ' + 'freq'.format(min_oscillations_measured)) + elif oversampling < 1: + isValid = False + logging.warning('oversampling rate is {}, recommend > 1: ' + 'increase sampling rate or decrease ' + 'demodulation frequency'.format(oversampling)) + + return isValid \ No newline at end of file diff --git a/qcodes/instrument_drivers/AlazarTech/acquisition_tools.py b/qcodes/instrument_drivers/AlazarTech/acquisition_tools.py new file mode 100644 index 00000000000..657f1f3be9f --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/acquisition_tools.py @@ -0,0 +1,49 @@ +import numpy as np +from scipy import signal + + +def sample_to_volt_u12(raw_samples, input_range_volts, bps): + # right_shift 16-bit sample by 4 to get 12 bit sample + shifted_samples = np.right_shift(raw_samples, 4) + + # Alazar calibration + code_zero = (1 << (bps - 1)) - 0.5 + code_range = (1 << (bps - 1)) - 0.5 + + # Convert to volts + volt_samples = (input_range_volts * + (shifted_samples - code_zero) / code_range) + + return volt_samples + + +def filter_win(rec, cutoff, sample_rate, numtaps): + nyq_rate = sample_rate / 2. + fir_coef = signal.firwin(numtaps, cutoff / nyq_rate) + filtered_rec = signal.lfilter(fir_coef, 1.0, rec) + return filtered_rec + + +def filter_ham(rec, cutoff, sample_rate, numtaps): + raise NotImplementedError + # sample_rate = self.sample_rate + # nyq_rate = sample_rate / 2. + # fir_coef = signal.firwin(numtaps, + # cutoff / nyq_rate, + # window="hamming") + # filtered_rec = 2 * signal.lfilter(fir_coef, 1.0, rec) + # return filtered_rec + + +def filter_ls(rec, cutoff, sample_rate, numtaps): + raise NotImplementedError + # sample_rate = self.sample_rate + # nyq_rate = sample_rate / 2. + # bands = [0, cutoff / nyq_rate, cutoff / nyq_rate, 1] + # desired = [1, 1, 0, 0] + # fir_coef = signal.firls(numtaps, + # bands, + # desired, + # nyq=nyq_rate) + # filtered_rec = 2 * signal.lfilter(fir_coef, 1.0, rec) + # return filtered_rec diff --git a/qcodes/instrument_drivers/AlazarTech/ave_controller.py b/qcodes/instrument_drivers/AlazarTech/ave_controller.py new file mode 100644 index 00000000000..ec1214d7e88 --- /dev/null +++ b/qcodes/instrument_drivers/AlazarTech/ave_controller.py @@ -0,0 +1,430 @@ +import logging +from .ATS import AcquisitionController +import numpy as np +import qcodes.instrument_drivers.AlazarTech.acq_helpers as helpers +from .acquisition_parameters import AcqVariablesParam, AlazarMultiArray + + +class HD_Averaging_Controller(AcquisitionController): + """ + This is the Acquisition Controller class which works with the ATS9360, + averaging over samples (limited by int_time and int_delay values), + records and buffers and demodulating with software reference signal(s). + + Args: + name: name for this acquisition_conroller as an instrument + alazar_name: name of the alazar instrument such that this + controller can communicate with the Alazar + demod_length (default 1): number of demodulation frequencies + filter (default 'win'): filter to be used to filter out double freq + component ('win' - window, 'ls' - least squared, 'ave' - averaging) + numtaps (default 101): number of freq components used in filter + chan_b (default False): whether there is also a second channel of data + to be processed and returned + **kwargs: kwargs are forwarded to the Instrument base class + + TODO(nataliejpg) test filter options + TODO(nataliejpg) finish implementation of channel b option + TODO(nataliejpg) what should be private? + TODO(nataliejpg) where should filter_dict live? + TODO(nataliejpg) demod_freq should be changeable number: maybe channels + """ + + filter_dict = {'win': 0, 'ls': 1, 'ave': 2} + + def __init__(self, name, alazar_name, demod_length=1, filter='win', + numtaps=101, chan_b=False, **kwargs): + self.filter_settings = {'filter': self.filter_dict[filter], + 'numtaps': numtaps} + self.chan_b = chan_b + self._demod_length = demod_length + self.number_of_channels = 2 + self.samples_per_record = None + self.sample_rate = None + super().__init__(name, alazar_name, **kwargs) + + self.add_parameter(name='acquisition', + demod_length=demod_length, + parameter_class=AlazarMultiArray, + names=('mag', 'phase')) + for i in range(demod_length): + self.add_parameter(name='demod_freq_{}'.format(i), + check_and_update_fn=self._update_demod_freq, + parameter_class=AcqVariablesParam) + self.add_parameter(name='int_time', + check_and_update_fn=self._update_int_time, + default_fn=self._int_time_default, + parameter_class=AcqVariablesParam) + self.add_parameter(name='int_delay', + check_and_update_fn=self._update_int_delay, + default_fn=self._int_delay_default, + parameter_class=AcqVariablesParam) + + self.samples_divisor = self._get_alazar().samples_divisor + + def _update_demod_freq(instr, value, param_name=None): + """ + Function to validate and update acquisiton parameter when + a demod_freq_ Parameter is changed + + Args: + value to update demodulation frequency to + + Kwargs: + param_name: used to update demod_freq list used for updating + septionts of acquisition parameter + + Checks: + 1e6 <= value <= 500e6 + number of oscilation measured using current int_tiume param value + at this demod frequency value + oversampling rate for this demodulation frequency + + Sets: + sample_rate attr of acq controller to be that of alazar + setpoints of acquisiton parameter + """ + if (value is None) or not (1e6 <= value <= 500e6): + raise ValueError('demod_freqs must be 1e6 <= value <= 500e6') + alazar = instr._get_alazar() + instr.sample_rate = alazar.get_sample_rate() + min_oscilations_measured = instr.int_time() * value + oversampling = instr.sample_rate / (2 * value) + if min_oscilations_measured < 10: + logging.warning('{} oscilations measured for largest ' + 'demod freq, recommend at least 10: ' + 'decrease sampling rate, take ' + 'more samples or increase demodulation ' + 'freq'.format(min_oscilations_measured)) + elif oversampling < 1: + logging.warning('oversampling rate is {}, recommend > 1: ' + 'increase sampling rate or decrease ' + 'demodulation frequency'.format(oversampling)) + demod_freqs = instr.get_demod_freqs() + current_demod_index = ([int(s) for s in param_name.split() + if s.isdigit()][0]) + demod_freqs[current_demod_index] = value + instr.acquisition.update_demod_setpoints(demod_freqs) + + def _update_int_time(instr, value, **kwargs): + """ + Function to validate value for int_time before setting parameter + value and update instr attributes. + + Args: + value to be validated and used for instrument attribute update + + Checks: + 0 <= value <= 0.1 seconds + number of oscilation measured in this time + oversampling rate + + Sets: + sample_rate attr of acq controller to be that of alazar + samples_per_record of acq controller + acquisition_kwarg['samples_per_record'] of acquisition param + """ + if (value is None) or not (0 <= value <= 0.1): + raise ValueError('int_time must be 0 <= value <= 1') + + alazar = instr._get_alazar() + instr.sample_rate = alazar.get_sample_rate() + if instr.get_max_demod_freq() is not None: + min_oscilations_measured = value * instr.get_max_demod_freq() + oversampling = instr.sample_rate / (2 * instr.get_max_demod_freq()) + if min_oscilations_measured < 10: + logging.warning('{} oscilations measured for largest ' + 'demod freq, recommend at least 10: ' + 'decrease sampling rate, take ' + 'more samples or increase demodulation ' + 'freq'.format(min_oscilations_measured)) + elif oversampling < 1: + logging.warning('oversampling rate is {}, recommend > 1: ' + 'increase sampling rate or decrease ' + 'demodulation frequency'.format(oversampling)) + if instr.int_delay() is None: + instr.int_delay.to_default() + + # update acquision kwargs and acq controller value + total_time = value + instr.int_delay() + samples_needed = total_time * instr.sample_rate + instr.samples_per_record = helpers.roundup( + samples_needed, instr.samples_divisor) + instr.acquisition.acquisition_kwargs.update( + samples_per_record=instr.samples_per_record) + + def _update_int_delay(instr, value, **kwargs): + """ + Function to validate value for int_delay before setting parameter + value and update instr attributes. + + Args: + value to be validated and used for instrument attribute update + + Checks: + 0 <= value <= 0.1 seconds + number of samples discarded >= numtaps + + Sets: + sample_rate attr of acq controller to be that of alazar + samples_per_record of acq controller + acquisition_kwarg['samples_per_record'] of acquisition param + setpoints of acquisiton param + """ + if (value is None) or not (0 <= value <= 0.1): + raise ValueError('int_delay must be 0 <= value <= 1') + alazar = instr._get_alazar() + instr.sample_rate = alazar.get_sample_rate() + samples_delay_min = (instr.filter_settings['numtaps'] - 1) + int_delay_min = samples_delay_min / instr.sample_rate + if value < int_delay_min: + logging.warning( + 'delay is less than recommended for filter choice: ' + '(expect delay >= {})'.format(int_delay_min)) + + # update acquision kwargs and acq controller value + total_time = value + (instr.int_time() or 0) + samples_needed = total_time * instr.sample_rate + instr.samples_per_record = helpers.roundup( + samples_needed, instr.samples_divisor) + instr.acquisition.acquisition_kwargs.update( + samples_per_record=instr.samples_per_record) + + def _int_delay_default(instr): + """ + Function to generate default int_delay value + + Returns: + minimum int_delay recommended for (numtaps - 1) + samples to be discarded as recommended for filter + """ + alazar = instr._get_alazar() + instr.sample_rate = alazar.get_sample_rate() + samp_delay = instr.filter_settings['numtaps'] - 1 + return samp_delay / instr.sample_rate + + def _int_time_default(instr): + """ + Function to generate defult int_time value + + Returns: + max total time for integration based on samples_per_record, + sample_rate and int_delay + """ + if instr.samples_per_record is (0 or None): + raise ValueError('Cannot set int_time to max if acq controller' + ' has 0 or None samples_per_record, choose a ' + 'value for int_time and samples_per_record will ' + 'be set accordingly') + alazar = instr._get_alazar() + instr.sample_rate = alazar.get_sample_rate() + total_time = ((instr.samples_per_record / instr.sample_rate) - + (instr.int_delay() or 0)) + return total_time + + def get_demod_freqs(self): + """ + Function to get all the demod_freq parameter values in a list, v hacky + + Returns: + numpy array of demodulation frequencies + """ + freqs = list(filter(None, [getattr(self, 'demod_freq_{}'.format(c))() + for c in range(self._demod_length)])) + return np.array(freqs) + + def get_max_demod_freq(self): + """ + Returns: + the largest demodulation frequency + + nb: really hacky and we should have channels in qcodes but we don't + (at time of writing) + """ + freqs = self.get_demod_freqs() + if len(freqs) > 0: + return max(freqs) + else: + return None + + def update_filter_settings(self, filter, numtaps): + """ + Updates the settings of the filter for filtering out + double frwuency component for demodulation. + + Args: + filter (str): filter type ('win' or 'ls') + numtaps (int): numtaps for filter + """ + self.filter_settings.update({'filter': self.filter_dict[filter], + 'numtaps': numtaps}) + + def update_acquisition_kwargs(self, **kwargs): + """ + Updates the kwargs to be used when + alazar_driver.acquisition() is called via a get call of the + acquisition AlazarMultiArray parameter. Should be used by the user for + updating averaging settings since the 'samples_per_record' + kwarg is updated via the int_time and int_delay parameters + + Kwargs (ints): + - records_per_buffer + - buffers_per_acquisition + - allocated_buffers + """ + if 'samples_per_record' in kwargs: + raise ValueError('With HD_Averaging_Controller ' + 'samples_per_record cannot be set manually ' + 'via update_acquisition_kwargs and should instead' + ' be set by setting int_time and int_delay') + self.acquisition.acquisition_kwargs.update(**kwargs) + + def pre_start_capture(self): + """ + Called before capture start to update Acquisition Controller with + alazar acquisition params and set up software wave for demodulation. + """ + alazar = self._get_alazar() + if self.samples_per_record != alazar.samples_per_record.get(): + raise Exception('acq controller samples per record does not match' + ' instrument value, most likely need ' + 'to set and check int_time and int_delay') + if self.sample_rate != alazar.get_sample_rate(): + raise Exception('acq controller sample rate does not match ' + 'instrument value, most likely need ' + 'to set and check int_time and int_delay') + + demod_freqs = self.get_demod_freqs() + if len(demod_freqs) == 0: + raise Exception('no demod_freqs set') + + self.records_per_buffer = alazar.records_per_buffer.get() + self.buffers_per_acquisition = alazar.buffers_per_acquisition.get() + self.board_info = alazar.get_idn() + self.buffer = np.zeros(self.samples_per_record * + self.records_per_buffer * + self.number_of_channels) + + integer_list = np.arange(self.samples_per_record) + angle_mat = 2 * np.pi * \ + np.outer(demod_freqs, integer_list) / self.sample_rate + self.cos_mat = np.cos(angle_mat) + self.sin_mat = np.sin(angle_mat) + + def pre_acquire(self): + pass + + def handle_buffer(self, data): + """ + Adds data from alazar to buffer (effectively averaging) + """ + self.buffer += data + + def post_acquire(self): + """ + Processes the data according to ATS9360 settings, splitting into + records and averaging over them, then applying demodulation fit + nb: currently only channel A + + Returns: + - magnitude numpy array of shape (demod_length, samples_used) + - phase numpy array of shape (demod_length, samples_used) + """ + + # for ATS9360 samples are arranged in the buffer as follows: + # S00A, S00B, S01A, S01B...S10A, S10B, S11A, S11B... + # where SXYZ is record X, sample Y, channel Z. + + # break buffer up into records and averages over them + reshaped_buf = self.buffer.reshape(self.records_per_buffer, + self.samples_per_record, + self.number_of_channels) + recordA = np.uint16(np.mean(reshaped_buf[:, :, 0], axis=0) / + self.buffers_per_acquisition) + + # TODO(nataliejpg): test above version works on alazar and + # compare performance + + # records_per_acquisition = (self.buffers_per_acquisition * + # self.records_per_buffer) + # recA = np.zeros(self.samples_per_record) + # for i in range(self.records_per_buffer): + # i0 = (i * self.samples_per_record * self.number_of_channels) + # i1 = (i0 + self.samples_per_record * self.number_of_channels) + # recA += self.buffer[i0:i1:self.number_of_channels] + # recordA = np.uint16(recA / records_per_acquisition) + + # do demodulation + magA, phaseA = self._fit(recordA) + + # same for chan b + if self.chan_b: + raise NotImplementedError('chan b code not complete') + + return magA, phaseA + + def _fit(self, rec): + """ + Applies volts conversion, demodulation fit, low bandpass filter + and integration limits to samples array + + Args: + rec (numpy array): record from alazar to be multiplied + with the software signal, filtered and limited + to integration limits shape = (samples_taken, ) + + Returns: + magnitude (numpy array): shape = (demod_length, ) + phase (numpy array): shape = (demod_length, ) + """ + # convert rec to volts + bps = self.board_info['bits_per_sample'] + if bps == 12: + volt_rec = helpers.sample_to_volt_u12(rec, bps) + else: + logging.warning('sample to volt conversion does not exist for' + ' bps != 12, centered raw samples returned') + volt_rec = rec - np.mean(rec) + + # volt_rec to matrix and multiply with demodulation signal matrices + volt_rec_mat = np.outer(np.ones(self._demod_length), volt_rec) + re_mat = np.multiply(volt_rec_mat, self.cos_mat) + im_mat = np.multiply(volt_rec_mat, self.sin_mat) + + # filter out higher freq component + cutoff = self.get_max_demod_freq() / 10 + if self.filter_settings['filter'] == 0: + re_filtered = helpers.filter_win(re_mat, cutoff, + self.sample_rate, + self.filter_settings['numtaps'], + axis=-1) + im_filtered = helpers.filter_win(im_mat, cutoff, + self.sample_rate, + self.filter_settings['numtaps'], + axis=-1) + elif self.filter_settings['filter'] == 1: + re_filtered = helpers.filter_ls(re_mat, cutoff, + self.sample_rate, + self.filter_settings['numtaps'], + axis=-1) + im_filtered = helpers.filter_ls(im_mat, cutoff, + self.sample_rate, + self.filter_settings['numtaps'], + axis=-1) + elif self.filter_settings['filter'] == 2: + re_filtered = re_mat + im_filtered = im_mat + + # apply integration limits + beginning = int(self.int_delay() * self.sample_rate) + end = beginning + int(self.int_time() * self.sample_rate) + + re_limited = re_filtered[:, beginning:end] + im_limited = im_filtered[:, beginning:end] + + # convert to magnitude and phase + complex_mat = re_limited + im_limited * 1j + magnitude = np.mean(abs(complex_mat), axis=-1) + phase = np.mean(np.angle(complex_mat, deg=True), axis=-1) + + return magnitude, phase diff --git a/qcodes/instrument_drivers/rohde_schwarz/ZNB.py b/qcodes/instrument_drivers/rohde_schwarz/ZNB.py index 792e121bc2e..7761a3375ae 100644 --- a/qcodes/instrument_drivers/rohde_schwarz/ZNB.py +++ b/qcodes/instrument_drivers/rohde_schwarz/ZNB.py @@ -5,6 +5,8 @@ from qcodes.utils import validators as vals from cmath import phase import numpy as np +import time +from functools import partial from qcodes import MultiParameter, ArrayParameter log = logging.getLogger(__name__) @@ -34,6 +36,7 @@ class FrequencySweepMagPhase(MultiParameter): TODO: - ability to choose for linear or db in magnitude return """ + def __init__(self, name, instrument, start, stop, npts, channel): super().__init__(name, names=("", ""), shapes=((), ())) self._instrument = instrument @@ -56,11 +59,11 @@ def set_sweep(self, start, stop, npts): def get(self): if not self._instrument._parent.rf_power(): - log.warning("RF output is off") + log.warning("RF output is off when getting mag phase") # it is possible that the instrument and qcodes disagree about # which parameter is measured on this channel instrument_parameter = self._instrument.vna_parameter() - if instrument_parameter != self._instrument._vna_parameter: + if instrument_parameter != self._instrument._vna_parameter: raise RuntimeError("Invalid parameter. Tried to measure " "{} got {}".format(self._instrument._vna_parameter, instrument_parameter)) @@ -72,7 +75,8 @@ def get(self): # need to ensure averaged result is returned for avgcount in range(self._instrument.avg()): self._instrument.write('INIT{}:IMM; *WAI'.format(self._channel)) - data_str = self._instrument.ask('CALC{}:DATA? SDAT'.format(self._channel)).split(',') + data_str = self._instrument.ask( + 'CALC{}:DATA? SDAT'.format(self._channel)).split(',') data_list = [float(v) for v in data_str] # data_list of complex numbers [re1,im1,re2,im2...] @@ -107,11 +111,13 @@ class FrequencySweep(ArrayParameter): get(): executes a sweep and returns magnitude and phase arrays """ + def __init__(self, name, instrument, start, stop, npts, channel): super().__init__(name, shape=(npts,), instrument=instrument, unit='dB', - label='{} magnitude'.format(instrument._vna_parameter), + label='{} magnitude'.format( + instrument._vna_parameter), setpoint_units=('Hz',), setpoint_names=('{}_frequency'.format(instrument._vna_parameter),)) self.set_sweep(start, stop, npts) @@ -126,11 +132,11 @@ def set_sweep(self, start, stop, npts): def get(self): if not self._instrument._parent.rf_power(): - log.warning("RF output is off") + log.warning("RF output is off when getting mag") # it is possible that the instrument and qcodes disagree about # which parameter is measured on this channel instrument_parameter = self._instrument.vna_parameter() - if instrument_parameter != self._instrument._vna_parameter: + if instrument_parameter != self._instrument._vna_parameter: raise RuntimeError("Invalid parameter. Tried to measure " "{} got {}".format(self._instrument._vna_parameter, instrument_parameter)) @@ -142,13 +148,14 @@ def get(self): # need to ensure averaged result is returned for avgcount in range(self._instrument.avg()): self._instrument.write('INIT{}:IMM; *WAI'.format(self._channel)) - data_str = self._instrument.ask('CALC{}:DATA? FDAT'.format(self._channel)) + data_str = self._instrument.ask( + 'CALC{}:DATA? FDAT'.format(self._channel)) data = np.array(data_str.rstrip().split(',')).astype('float64') if self._instrument.format() in ['Polar', 'Complex', 'Smith', 'Inverse Smith']: log.warning("QCoDeS Dataset does not currently support Complex " "values. Will discard the imaginary part.") - data = data[0::2] + 1j*data[1::2] + data = data[0::2] + 1j * data[1::2] self._instrument._parent.cont_meas_on() self._save_val(data) return data @@ -173,7 +180,7 @@ def __init__(self, parent, name, channel): self.add_parameter(name='vna_parameter', label='VNA parameter', get_cmd="CALC{}:PAR:MEAS? '{}'".format(self._instrument_channel, - self._tracename), + self._tracename), get_parser=self._strip) self.add_parameter(name='power', label='Power', @@ -220,6 +227,10 @@ def __init__(self, parent, name, channel): get_cmd='SENS{}:SWE:POIN?'.format(n), set_cmd=self._set_npts, get_parser=int) + self.add_parameter(name='status', + get_cmd='CONF:CHAN{}:MEAS?'.format(n), + set_cmd='CONF:CHAN{}:MEAS {{}}'.format(n), + get_parser=int) self.add_parameter(name='format', get_cmd='CALC{}:FORM?'.format(n), set_cmd=self._set_format, @@ -281,7 +292,8 @@ def _set_format(self, val): channel = self._instrument_channel self.write('CALC{}:FORM {}'.format(channel, val)) self.trace.unit = unit_mapping[val] - self.trace.label = "{} {}".format(self.vna_parameter(), label_mapping[val]) + self.trace.label = "{} {}".format( + self.vna_parameter(), label_mapping[val]) def _strip(self, var): "Strip newline and quotes from instrument reply" @@ -294,11 +306,13 @@ def _set_start(self, val): npts = self.npts() if val >= stop: - raise ValueError("Stop frequency must be larger than start frequency.") + raise ValueError( + "Stop frequency must be larger than start frequency.") # we get start as the vna may not be able to set it to the exact value provided start = self.start() if val != start: - log.warning("Could not set start to {} setting it to {}".format(val, start)) + log.warning( + "Could not set start to {} setting it to {}".format(val, start)) # update setpoints for FrequencySweep param self.trace.set_sweep(start, stop, npts) self.trace_mag_phase.set_sweep(start, stop, npts) @@ -308,12 +322,14 @@ def _set_stop(self, val): start = self.start() npts = self.npts() if val <= start: - raise ValueError("Stop frequency must be larger than start frequency.") + raise ValueError( + "Stop frequency must be larger than start frequency.") self.write('SENS{}:FREQ:STOP {:.4f}'.format(channel, val)) # we get stop as the vna may not be able to set it to the exact value provided stop = self.stop() if val != stop: - log.warning("Could not set stop to {} setting it to {}".format(val, stop)) + log.warning( + "Could not set stop to {} setting it to {}".format(val, stop)) # update setpoints for FrequencySweep param self.trace.set_sweep(start, stop, npts) self.trace_mag_phase.set_sweep(start, stop, npts) @@ -345,6 +361,14 @@ 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): """ @@ -364,6 +388,7 @@ class ZNB(VisaInstrument): TODO: - check initialisation settings and test functions """ + def __init__(self, name: str, address: str, init_s_params: bool=True, **kwargs): super().__init__(name=name, address=address, **kwargs) @@ -385,15 +410,14 @@ def __init__(self, name: str, address: str, init_s_params: bool=True, **kwargs): self._max_freq = 20e9 self._min_freq = 100e3 self.add_parameter(name='num_ports', - get_cmd='INST:PORT:COUN?', - get_parser=int) + get_cmd='INST:PORT:COUN?', + get_parser=int) num_ports = self.num_ports() self.add_parameter(name='rf_power', get_cmd='OUTP1?', set_cmd='OUTP1 {}', val_mapping={True: '1\n', False: '0\n'}) - self.add_function('reset', call_cmd='*RST') self.add_function('tooltip_on', call_cmd='SYST:ERR:DISP ON') self.add_function('tooltip_off', call_cmd='SYST:ERR:DISP OFF') @@ -402,8 +426,10 @@ def __init__(self, name: str, address: str, init_s_params: bool=True, **kwargs): self.add_function('update_display_once', call_cmd='SYST:DISP:UPD ONCE') self.add_function('update_display_on', call_cmd='SYST:DISP:UPD ON') self.add_function('update_display_off', call_cmd='SYST:DISP:UPD OFF') - self.add_function('display_sij_split', call_cmd='DISP:LAY GRID;:DISP:LAY:GRID {},{}'.format(num_ports, num_ports)) - self.add_function('display_single_window', call_cmd='DISP:LAY GRID;:DISP:LAY:GRID 1,1') + self.add_function('display_sij_split', call_cmd='DISP:LAY GRID;:DISP:LAY:GRID {},{}'.format( + num_ports, num_ports)) + self.add_function('display_single_window', + call_cmd='DISP:LAY GRID;:DISP:LAY:GRID 1,1') self.add_function('rf_off', call_cmd='OUTP1 OFF') self.add_function('rf_on', call_cmd='OUTP1 ON') self.clear_channels() @@ -412,8 +438,8 @@ def __init__(self, name: str, address: str, init_s_params: bool=True, **kwargs): self.add_submodule("channels", channels) if init_s_params: n = 1 - for i in range(1, num_ports+1): - for j in range(1, num_ports+1): + for i in range(1, num_ports + 1): + for j in range(1, num_ports + 1): ch_name = 'S' + str(i) + str(j) self.add_channel(ch_name) n += 1 @@ -432,14 +458,91 @@ def display_grid(self, rows: int, cols: int): """ self.write('DISP:LAY GRID;:DISP:LAY:GRID {},{}'.format(rows, cols)) - def add_channel(self, vna_parameter: str): n_channels = len(self.channels) channel = ZNBChannel(self, vna_parameter, n_channels + 1) + self.write( + 'SOUR{}:FREQ1:CONV:ARB:IFR 1, 1, 0, SWE'.format(n_channels + 1)) + self.write( + 'SOUR{}:FREQ2:CONV:ARB:IFR 1, 1, 0, SWE'.format(n_channels + 1)) + self.write('SOUR{}:POW1:OFFS 0, CPAD'.format(n_channels + 1)) + self.write('SOUR{}:POW2:OFFS 0, CPAD'.format(n_channels + 1)) + self.write('SOUR{}:POW1:PERM OFF'.format(n_channels + 1)) + self.write('SOUR{}:POW:GEN1:PERM OFF'.format(n_channels + 1)) + self.write('SOUR{}:POW:GEN1:STAT OFF'.format(n_channels + 1)) + self.write('SOUR{}:POW2:STAT ON'.format(n_channels + 1)) self.channels.append(channel) if n_channels == 0: self.display_single_window() + def count_external_generators(self): + num = self.ask('SYST:COMM:RDEV:GEN:COUN?').strip() + return int(num) + + def set_external_generator(self, address, gen=1, gen_name="ext gen 1", + driver="SGS100A", interface="VXI-11"): + self.write('SYST:COMM:RDEV:GEN{:.0f}:DEF "{}", "{}", "{}", "{}", OFF, ON'.format( + gen, gen_name, driver, interface, address)) + + def get_external_generator_setup(self, num=1): + setup = self.ask( + 'SYSTem:COMMunicate:RDEVice:GEN{:.0f}:DEF?'.format(num)).strip() + return setup + + def clear_external_generator(self, num=1): + self.write('SYST:COMM:RDEV:GEN{:.0f}:DEL'.format(num)) + + def get_external_generator_numbers(self): + cat = self.ask('SYST:COMM:RDEV:GEN1:CAT?').strip() + return cat + + def add_spectroscopy_channel(self, generator_address, + vna_parameter="B2G1SAM"): + """ + Adds a generator and uses it to generate a fixed frequency tone, the + response at this frequency is read out at port 2 which is also set to + be fixed freq. Port 1 is set as the port for sweeping etc""" + self.set_external_generator(generator_address) + self.add_channel(vna_parameter) + chan_num = len(self.channels) + self.write('SOUR{}:POW2:STAT OFF'.format(chan_num)) + time.sleep(0.2) + self.write('SOUR{}:POW:GEN1:PERM ON'.format(chan_num)) + time.sleep(0.2) + self.write('SOUR{}:POW1:PERM ON'.format(chan_num)) + time.sleep(0.2) + self.write('SOUR{}:POW:GEN1:STAT ON'.format(chan_num)) + time.sleep(0.2) + self.write('ROSC EXT') + self.add_parameter( + 'readout_freq', + set_cmd=partial(self._set_readout_freq, chan_num), + get_cmd=partial(self._get_readout_freq, chan_num), + get_parser=float, + vals=vals.Numbers(self._min_freq, self._max_freq)) + self.add_parameter( + 'readout_power', + set_cmd=partial(self._set_readout_pow, chan_num), + get_cmd=partial(self._get_readout_pow, chan_num), + get_parser=int, + vals=vals.Numbers(-150, 25)) + + + def _set_readout_freq(self, chan_num, freq): + self.write( + 'SOUR{}:FREQ:CONV:ARB:EFR1 ON, 0, 1, {:.6f}, CW'.format(chan_num, freq)) + self.write( + 'SOUR{}:FREQ2:CONV:ARB:IFR 0, 1, {:.6f}, CW'.format(chan_num, freq)) + + def _get_readout_freq(self, chan_num): + return self.ask('SOUR:FREQ:CONV:ARB:EFR1?').split(',')[3] + + def _set_readout_pow(self, chan_num, pow): + self.write('SOUR{}:POW:GEN1:OFFS {:.3f}, ONLY'.format(chan_num, pow)) + self.write('SOUR{}:POW2:OFFS {:.3f}, ONLY'.format(chan_num, pow)) + + def _get_readout_pow(self, chan_num): + return self.ask('SOUR{}:POW:GEN1:OFFS?'.format(chan_num)).split(',')[0] def _set_default_values(self): for channel in self.channels: diff --git a/qcodes/loops.py b/qcodes/loops.py index 5a898e0ceec..c622839410b 100644 --- a/qcodes/loops.py +++ b/qcodes/loops.py @@ -493,6 +493,8 @@ def _parameter_arrays(self, action): else: units = tuple(['']*len(names)) num_arrays = len(names) + num_units = len(units) + assert num_arrays == num_units shapes = getattr(action, 'shapes', None) sp_vals = getattr(action, 'setpoints', None) sp_names = getattr(action, 'setpoint_names', None) @@ -553,7 +555,7 @@ def _fill_blank(self, inputs, blanks): elif len(inputs) == len(blanks): return inputs else: - raise ValueError('Wrong number of inputs supplied') + raise ValueError('Wrong number of inputs supplied got {} expected {}'.format(len(inputs), len(blanks))) def _make_setpoint_array(self, shape, i, prev_setpoints, vals, name, label, unit): diff --git a/qcodes/measure.py b/qcodes/measure.py index 74443d3b587..c07150e6b3f 100644 --- a/qcodes/measure.py +++ b/qcodes/measure.py @@ -31,6 +31,9 @@ def run_temp(self, **kwargs): """ return self.run(quiet=True, location=False, **kwargs) + def get_data_set(self, *args, **kwargs): + return self._dummyLoop.get_data_set(*args, **kwargs) + def run(self, use_threads=False, quiet=False, station=None, **kwargs): """ Run the actions in this measurement and return their data as a DataSet diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 97b58517877..3d6e7feaba9 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -5,7 +5,7 @@ import pyqtgraph as pg import pyqtgraph.multiprocess as pgmp from pyqtgraph.multiprocess.remoteproxy import ClosedError -import qcodes.utils.helpers +from pyqtgraph import QtGui # note that pyqtgraph still uses the old pyqt4 layout import warnings from collections import namedtuple, deque @@ -30,6 +30,10 @@ class QtPlot(BasePlot): figsize: (width, height) tuple in pixels to pass to GraphicsWindow default (1000, 600) + fig_x_pos: fraction of screen width to place the figure at + 0 is all the way to the left and + 1 is all the way to the right. + default None let qt decide. interval: period in seconds between update checks default 0.25 theme: tuple of (foreground_color, background_color), where each is @@ -51,7 +55,8 @@ class QtPlot(BasePlot): plots = deque(maxlen=qcodes.config['gui']['pyqtmaxplots']) def __init__(self, *args, figsize=(1000, 600), interval=0.25, - window_title='', theme=((60, 60, 60), 'w'), show_window=True, remote=True, **kwargs): + window_title='', theme=((60, 60, 60), 'w'), show_window=True, remote=True, fig_x_position=None, + **kwargs): super().__init__(interval) if 'windowTitle' in kwargs.keys(): @@ -67,7 +72,6 @@ def __init__(self, *args, figsize=(1000, 600), interval=0.25, else: # overrule the remote pyqtgraph class self.rpg = pg - self.qc_helpers = qcodes.utils.helpers try: self.win = self.rpg.GraphicsWindow(title=window_title) except ClosedError as err: @@ -80,6 +84,10 @@ def __init__(self, *args, figsize=(1000, 600), interval=0.25, raise err self.win.setBackground(theme[1]) self.win.resize(*figsize) + if fig_x_position: + _, _, width, height = QtGui.QDesktopWidget().screenGeometry().getCoords() + y_pos = self.win.y() + self.win.move(width * fig_x_position, y_pos) self.subplots = [self.add_subplot()] if args or kwargs: @@ -98,7 +106,6 @@ def _init_qt(cls): pg.mkQApp() cls.proc = pgmp.QtProcess() # pyqtgraph multiprocessing cls.rpg = cls.proc._import('pyqtgraph') - cls.qc_helpers = cls.proc._import('qcodes.utils.helpers') def clear(self): """ diff --git a/qcodes/plots/qcmatplotlib_viewer_widget.py b/qcodes/plots/qcmatplotlib_viewer_widget.py new file mode 100644 index 00000000000..968633dc876 --- /dev/null +++ b/qcodes/plots/qcmatplotlib_viewer_widget.py @@ -0,0 +1,246 @@ +from matplotlib.widgets import Cursor +import mplcursors +import numpy as np +import matplotlib.pyplot as plt + +from PyQt5 import QtWidgets +from .base import BasePlot + +class ClickWidget(BasePlot): + + def __init__(self, dataset): + super().__init__() + data = {} + self.expand_trace(args=[dataset], kwargs=data) + self.traces = [] + + data['xlabel'] = self.get_label(data['x']) + data['ylabel'] = self.get_label(data['y']) + data['zlabel'] = self.get_label(data['z']) + data['xaxis'] = data['x'].ndarray[0, :] + data['yaxis'] = data['y'].ndarray + self.traces.append({ + 'config': data, + }) + self.fig = plt.figure() + + self._lines = [] + self._datacursor = [] + self._cid = 0 + + hbox = QtWidgets.QHBoxLayout() + self.fig.canvas.setLayout(hbox) + hspace = QtWidgets.QSpacerItem(0, + 0, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + vspace = QtWidgets.QSpacerItem(0, + 0, + QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.Expanding) + hbox.addItem(hspace) + + vbox = QtWidgets.QVBoxLayout() + self.crossbtn = QtWidgets.QCheckBox('Cross section') + self.crossbtn.setToolTip("Display extra subplots with selectable cross sections " + "or sums along axis.") + self.sumbtn = QtWidgets.QCheckBox('Sum') + self.sumbtn.setToolTip("Display sums or cross sections.") + + self.savehmbtn = QtWidgets.QPushButton('Save Heatmap') + self.savehmbtn.setToolTip("Save heatmap as a file (PDF)") + self.savexbtn = QtWidgets.QPushButton('Save Vert') + self.savexbtn.setToolTip("Save vertical cross section or sum as a file (PDF)") + self.saveybtn = QtWidgets.QPushButton('Save Horz') + self.savexbtn.setToolTip("Save horizontal cross section or sum as a file (PDF)") + + self.crossbtn.toggled.connect(self.toggle_cross) + self.sumbtn.toggled.connect(self.toggle_sum) + + self.savehmbtn.pressed.connect(self.save_heatmap) + self.savexbtn.pressed.connect(self.save_subplot_x) + self.saveybtn.pressed.connect(self.save_subplot_y) + + self.toggle_cross() + self.toggle_sum() + + vbox.addItem(vspace) + vbox.addWidget(self.crossbtn) + vbox.addWidget(self.sumbtn) + vbox.addWidget(self.savehmbtn) + vbox.addWidget(self.savexbtn) + vbox.addWidget(self.saveybtn) + + hbox.addLayout(vbox) + + @staticmethod + def full_extent(ax, pad=0.0): + """Get the full extent of an axes, including axes labels, tick labels, and + titles.""" + # for text objects we only include them if they are non empty. + # empty ticks may be rendered outside the figure + from matplotlib.transforms import Bbox + items = [] + items += [ax.xaxis.label, ax.yaxis.label, ax.title] + items = [item for item in items if item.get_text()] + items.append(ax) + bbox = Bbox.union([item.get_window_extent() for item in items]) + + return bbox.expanded(1.0 + pad, 1.0 + pad) + + def save_subplot(self, axnumber, savename, saveformat='pdf'): + extent = self.full_extent(self.ax[axnumber]).transformed(self.fig.dpi_scale_trans.inverted()) + full_title = "{}.{}".format(savename, saveformat) + self.fig.savefig(full_title, bbox_inches=extent) + + def save_subplot_x(self): + title = self.get_default_title() + label, unit = self._get_label_and_unit(self.traces[0]['config']['xlabel']) + if self.sumbtn.isChecked(): + title += " sum over {}".format(label) + else: + title += " cross section {} = {} {}".format(label, + self.traces[0]['config']['xpos'], + unit) + self.save_subplot(axnumber=(0, 1), savename=title) + + def save_subplot_y(self): + title = self.get_default_title() + label, unit = self._get_label_and_unit(self.traces[0]['config']['ylabel']) + if self.sumbtn.isChecked(): + title += " sum over {}".format(label) + else: + title += " cross section {} = {} {}".format(label, + self.traces[0]['config']['xpos'], + unit) + self.save_subplot(axnumber=(1, 0), savename=title) + + def save_heatmap(self): + title = self.get_default_title() + " heatmap" + self.save_subplot(axnumber=(0, 0), savename=title) + + def _update_label(self, ax, axletter, label, extra=None): + + if type(label) == tuple and len(label) == 2: + label, unit = label + else: + unit = "" + axsetter = getattr(ax, "set_{}label".format(axletter)) + if extra: + axsetter(extra + "{} ({})".format(label, unit)) + else: + axsetter("{} ({})".format(label, unit)) + + @staticmethod + def _get_label_and_unit(config): + if type(config) == tuple and len(config) == 2: + label, unit = config + else: + unit = "" + label = config + return label, unit + + def toggle_cross(self): + self.remove_plots() + self.fig.clear() + if self._cid: + self.fig.canvas.mpl_disconnect(self._cid) + if self.crossbtn.isChecked(): + self.sumbtn.setEnabled(True) + self.savexbtn.setEnabled(True) + self.saveybtn.setEnabled(True) + self.ax = np.empty((2, 2), dtype='O') + self.ax[0, 0] = self.fig.add_subplot(2, 2, 1) + self.ax[0, 1] = self.fig.add_subplot(2, 2, 2) + self.ax[1, 0] = self.fig.add_subplot(2, 2, 3) + self._cid = self.fig.canvas.mpl_connect('button_press_event', self._click) + self._cursor = Cursor(self.ax[0, 0], useblit=True, color='black') + self.toggle_sum() + figure_rect = (0, 0, 1, 1) + else: + self.sumbtn.setEnabled(False) + self.savexbtn.setEnabled(False) + self.saveybtn.setEnabled(False) + self.ax = np.empty((1, 1), dtype='O') + self.ax[0, 0] = self.fig.add_subplot(1, 1, 1) + figure_rect = (0, 0.0, 0.75, 1) + self.ax[0, 0].pcolormesh(self.traces[0]['config']['x'], + self.traces[0]['config']['y'], + self.traces[0]['config']['z'], + edgecolor='face') + self._update_label(self.ax[0, 0], 'x', self.traces[0]['config']['xlabel']) + self._update_label(self.ax[0, 0], 'y', self.traces[0]['config']['ylabel']) + self.fig.tight_layout(rect=figure_rect) + self.fig.canvas.draw_idle() + + def toggle_sum(self): + self.remove_plots() + if not self.crossbtn.isChecked(): + return + self.ax[1, 0].clear() + self.ax[0, 1].clear() + if self.sumbtn.isChecked(): + self._cursor.set_active(False) + self.ax[1, 0].set_ylim(0, self.traces[0]['config']['z'].sum(axis=0).max() * 1.05) + self.ax[0, 1].set_xlim(0, self.traces[0]['config']['z'].sum(axis=1).max() * 1.05) + self._update_label(self.ax[1, 0], 'x', self.traces[0]['config']['xlabel']) + self._update_label(self.ax[1, 0], 'y', self.traces[0]['config']['zlabel'], extra='sum of ') + + self._update_label(self.ax[0, 1], 'x', self.traces[0]['config']['zlabel'], extra='sum of ') + self._update_label(self.ax[0, 1], 'y', self.traces[0]['config']['ylabel']) + + self._lines.append(self.ax[0, 1].plot(self.traces[0]['config']['z'].sum(axis=1), + self.traces[0]['config']['yaxis'], + color='C0', + marker='.')[0]) + self.ax[0, 1].set_title("") + self._lines.append(self.ax[1, 0].plot(self.traces[0]['config']['xaxis'], + self.traces[0]['config']['z'].sum(axis=0), + color='C0', + marker='.')[0]) + self.ax[1, 0].set_title("") + self._datacursor = mplcursors.cursor(self._lines, multiple=False) + else: + self._cursor.set_active(True) + self._update_label(self.ax[1, 0], 'x', self.traces[0]['config']['xlabel']) + self._update_label(self.ax[1, 0], 'y', self.traces[0]['config']['zlabel']) + + self._update_label(self.ax[0, 1], 'x', self.traces[0]['config']['zlabel']) + self._update_label(self.ax[0, 1], 'y', self.traces[0]['config']['ylabel']) + + self.ax[1, 0].set_ylim(0, self.traces[0]['config']['z'].max() * 1.05) + self.ax[0, 1].set_xlim(0, self.traces[0]['config']['z'].max() * 1.05) + self.fig.canvas.draw_idle() + + def remove_plots(self): + for line in self._lines: + line.remove() + self._lines = [] + if self._datacursor: + self._datacursor.remove() + + def _click(self, event): + + if event.inaxes == self.ax[0, 0] and not self.sumbtn.isChecked(): + xpos = (abs(self.traces[0]['config']['xaxis'] - event.xdata)).argmin() + ypos = (abs(self.traces[0]['config']['yaxis'] - event.ydata)).argmin() + self.remove_plots() + + self._lines.append(self.ax[0, 1].plot(self.traces[0]['config']['z'][:, xpos], + self.traces[0]['config']['yaxis'], + color='C0', + marker='.')[0]) + xlabel, xunit = self._get_label_and_unit(self.traces[0]['config']['xlabel']) + self.ax[0, 1].set_title("{} = {} {} ".format(xlabel, self.traces[0]['config']['xaxis'][xpos], xunit), + fontsize='small') + self.traces[0]['config']['xpos'] = self.traces[0]['config']['xaxis'][xpos] + self._lines.append(self.ax[1, 0].plot(self.traces[0]['config']['xaxis'], + self.traces[0]['config']['z'][ypos, :], + color='C0', + marker='.')[0]) + ylabel, yunit = self._get_label_and_unit(self.traces[0]['config']['ylabel']) + self.ax[1, 0].set_title("{} = {} {}".format(ylabel, self.traces[0]['config']['yaxis'][ypos], yunit), + fontsize='small') + self.traces[0]['config']['ypos'] = self.traces[0]['config']['yaxis'][ypos] + self._datacursor = mplcursors.cursor(self._lines, multiple=False) + self.fig.canvas.draw_idle() diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index 6962fb9f7df..1a1c68d5ade 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -446,7 +446,6 @@ def compare_dictionaries(dict_1, dict_2, dicts_equal = False return dicts_equal, dict_differences - def warn_units(class_name, instance): logging.warning('`units` is deprecated for the `' + class_name + '` class, use `unit` instead. ' + repr(instance)) diff --git a/qcodes/utils/qcodes_device_annotator.py b/qcodes/utils/qcodes_device_annotator.py new file mode 100644 index 00000000000..1457c2cf7b6 --- /dev/null +++ b/qcodes/utils/qcodes_device_annotator.py @@ -0,0 +1,418 @@ +# A device image plotter + +import sys +import os +import json +import glob +import PyQt5.QtWidgets as qt +import PyQt5.QtGui as gui +import PyQt5.QtCore as core + +from shutil import copyfile +import copy + +class MakeDeviceImage(qt.QWidget): + """ + Class for clicking and adding labels + """ + + def __init__(self, folder, station): + + super().__init__() + + self.folder = folder + self.station = station + + # BACKEND + self._data = {} + self.filename = None + + # FRONTEND + + grid = qt.QGridLayout() + self.setLayout(grid) + self.textfont = None + self.imageCanvas = qt.QLabel() + self.imageCanvas.setToolTip("Left click to insert a label and right " + "click to insert an annotation.") + self.loadButton = qt.QPushButton('Load image') + + self.okButton = qt.QPushButton('Save and close') + self.removeButton = qt.QPushButton('Remove') + self.removeButton.setToolTip("Remove annotation and label for this " + "parameter") + self.fontButton = qt.QPushButton('Font') + self.labeltitle = qt.QLabel('Label:') + self.labelfield = qt.QLineEdit() + self.labelfield.setText('') + self.labelfield.setToolTip("String to be used as label. Defaults to " + "instrument_parameter") + self.formattertitle = qt.QLabel('Formatter:') + self.formatterfield = qt.QLineEdit() + self.formatterfield.setText('') + self.formatterfield.setToolTip("Formatter to be used for this parameter:\n" + "Uses new style python formatters. I.e.\n" + "':.4f' standard formatter with 4 digits after the decimal point\n" + "':.2e' exponential formatter with 2 digits after the decimal point\n" + "Leave blank for default. \n" + "See www.pyformat.info for more examples") + + self.loadButton.clicked.connect(self.loadimage) + self.imageCanvas.mousePressEvent = self.set_label_or_annotation + self.imageCanvas.setStyleSheet('background-color: white') + self.okButton.clicked.connect(self.saveAndClose) + self.removeButton.clicked.connect(self.remove_label_and_annotation) + self.fontButton.clicked.connect(self.select_font) + + self.treeView = qt.QTreeView() + self.model = gui.QStandardItemModel() + self.model.setHorizontalHeaderLabels([self.tr("Instruments")]) + self.addStation(self.model, station) + self.treeView.setModel(self.model) + self.treeView.sortByColumn(0, core.Qt.AscendingOrder) + self.treeView.setSortingEnabled(True) + self.treeView.clicked.connect(self.selection_changed) + grid.addWidget(self.imageCanvas, 0, 0, 4, 7) + grid.addWidget(self.loadButton, 4, 0) + grid.addWidget(self.labeltitle, 4, 1) + grid.addWidget(self.labelfield, 4, 2) + grid.addWidget(self.formattertitle, 4, 3) + grid.addWidget(self.formatterfield, 4, 4) + grid.addWidget(self.fontButton, 4, 6) + grid.addWidget(self.removeButton, 4, 9) + grid.addWidget(self.okButton, 4, 10) + grid.addWidget(self.treeView, 0, 7, 4, 4) + + self.resize(600, 400) + self.move(100, 100) + self.setWindowTitle('Generate annotated device image') + self.show() + + def select_font(self): + + font, ok = qt.QFontDialog.getFont() + if ok: + self.textfont = font + + def addStation(self, parent, station): + + for inst in station.components: + item = gui.QStandardItem(inst) + item.setEditable(False) + item.setSelectable(False) + parent.appendRow(item) + for param in station[inst].parameters: + paramitem = gui.QStandardItem(param) + paramitem.setEditable(False) + item.appendRow(paramitem) + + def loadimage(self): + """ + Select an image from disk. + """ + fd = qt.QFileDialog() + filename = fd.getOpenFileName(self, 'Select device image', + os.getcwd(), + "Image files(*.jpg *.png *.jpeg)") + self.filename = filename[0] + self.pixmap = gui.QPixmap(filename[0]) + width = self.pixmap.width() + height = self.pixmap.height() + + self.imageCanvas.setPixmap(self.pixmap) + + # fix the image scale, so that the pixel values of the mouse are + # unambiguous + self.imageCanvas.setMaximumWidth(width) + self.imageCanvas.setMaximumHeight(height) + + def selection_changed(self): + if not self.treeView.selectedIndexes(): + return + selected = self.treeView.selectedIndexes()[0] + selected_instrument = selected.parent().data() + selected_parameter = selected.data() + self.labelfield.setText("{}_{} ".format(selected_instrument, selected_parameter)) + + def set_label_or_annotation(self, event): + insertlabel = False + insertannotation = False + if event.button() == core.Qt.LeftButton: + insertlabel = True + elif event.button() == core.Qt.RightButton: + insertannotation = True + # verify valid + if not self.treeView.selectedIndexes(): + return + selected = self.treeView.selectedIndexes()[0] + selected_instrument = selected.parent().data() + selected_parameter = selected.data() + self.click_x = event.pos().x() + self.click_y = event.pos().y() + + # update the data + if selected_instrument not in self._data.keys(): + self._data[selected_instrument] = {} + if selected_parameter not in self._data[selected_instrument].keys(): + self._data[selected_instrument][selected_parameter] = {} + + if insertlabel: + self._data[selected_instrument][selected_parameter]['labelpos'] = (self.click_x, self.click_y) + self._data[selected_instrument][selected_parameter]['labelstring'] = self.labelfield.text() + elif insertannotation: + self._data[selected_instrument][selected_parameter]['annotationpos'] = (self.click_x, self.click_y) + if self.formatterfield.text(): + formatstring = '{' + self.formatterfield.text() + "}" + self._data[selected_instrument][selected_parameter]['annotationformatter'] = formatstring + self._data[selected_instrument][selected_parameter]['value'] = formatstring + else: + self._data[selected_instrument][selected_parameter]['value'] = 'NaN' + + + self._data['font'] = {} + if not self.textfont: + self._data['font']['family'] = 'decorative' + else: + self._data['font']['family'] = self.textfont.family() + self._data['font']['label_size'] = self.textfont.pointSize() + # draw it + self.imageCanvas, _ = self._renderImage(self._data, + self.imageCanvas, + self.filename) + + def remove_label_and_annotation(self): + selected = self.treeView.selectedIndexes()[0] + selected_instrument = selected.parent().data() + selected_parameter = selected.data() + if selected_parameter in self._data[selected_instrument].keys(): + self._data[selected_instrument][selected_parameter] = {} + # draw it + self.imageCanvas, _ = self._renderImage(self._data, + self.imageCanvas, + self.filename) + + def saveAndClose(self): + """ + Save and close + """ + if self.filename is None: + return + + fileformat = self.filename.split('.')[-1] + rawpath = os.path.join(self.folder, 'deviceimage_raw.'+fileformat) + copyfile(self.filename, rawpath) + + # Now forget about the original + self.filename = rawpath + + self.close() + + @staticmethod + def _renderImage(fulldata, canvas, filename, title=None): + """ + Render an image + """ + + pixmap = gui.QPixmap(filename) + width = pixmap.width() + height = pixmap.height() + + label_size = min(height/30, width/30) + spacing = int(label_size * 0.2) + + painter = gui.QPainter(pixmap) + data = copy.deepcopy(fulldata) + try: + fontdict = data.pop('font') + label_size = fontdict.get('label_size', label_size) + textfont = gui.QFont(fontdict['family'], label_size) + except: + textfont = gui.QFont('Decorative', label_size) + fontmetric = gui.QFontMetrics(textfont) + if title: + painter.setBrush(gui.QColor(255, 255, 255, 100)) + textwidth = fontmetric.width(title) + rectangle_width = textwidth + 2 * spacing + rectangle_height = label_size + 2 * spacing + painter.drawRect(0, + 0, + rectangle_width, + rectangle_height) + painter.drawText(core.QRectF(spacing, spacing, + rectangle_width, rectangle_height), + core.Qt.AlignTop + core.Qt.AlignLeft, + title) + + for instrument, parameters in data.items(): + for parameter, paramsettings in parameters.items(): + + if 'labelpos' in paramsettings: + if paramsettings.get('labelstring'): + label_string = paramsettings.get('labelstring') + else: + label_string = "{}_{} ".format(instrument, parameter) + if paramsettings.get('update'): + #parameters that are sweeped should be red. + painter.setBrush(gui.QColor(255, 0, 0, 100)) + else: + painter.setBrush(gui.QColor(255, 255, 255, 100)) + (lx, ly) = paramsettings['labelpos'] + textwidth = fontmetric.width(label_string) + rectangle_start_x = lx - spacing + rectangle_start_y = ly - spacing + rectangle_width = textwidth+2*spacing + rectangle_height = label_size+2*spacing + painter.drawRect(rectangle_start_x, + rectangle_start_y, + rectangle_width, + rectangle_height) + painter.setBrush(gui.QColor(25, 25, 25)) + + painter.setFont(textfont) + painter.drawText(core.QRectF(rectangle_start_x, rectangle_start_y, + rectangle_width, rectangle_height), + core.Qt.AlignCenter, + label_string) + + if 'annotationpos' in paramsettings: + (ax, ay) = paramsettings['annotationpos'] + annotationstring = paramsettings['value'] + + textwidth = fontmetric.width(annotationstring) + rectangle_start_x = ax - spacing + rectangle_start_y = ay - spacing + rectangle_width = textwidth + 2 * spacing + rectangle_height = label_size + 2 * spacing + painter.setBrush(gui.QColor(255, 255, 255, 100)) + painter.drawRect(rectangle_start_x, + rectangle_start_y, + rectangle_width, + rectangle_height) + painter.setBrush(gui.QColor(50, 50, 50)) + painter.setFont(textfont) + painter.drawText(core.QRectF(rectangle_start_x, rectangle_start_y, + rectangle_width, rectangle_height), + core.Qt.AlignCenter, + annotationstring) + + canvas.setPixmap(pixmap) + + return canvas, pixmap + + +class DeviceImage: + + """ + Manage an image of a device + """ + + def __init__(self, folder, station): + + self._data = {} + self.filename = None + self.folder = folder + self.station = station + + def annotateImage(self): + """ + Launch a Qt Widget to click + """ + if not qt.QApplication.instance(): + app = qt.QApplication(sys.argv) + else: + app = qt.QApplication.instance() + imagedrawer = MakeDeviceImage(self.folder, self.station) + app.exec_() + imagedrawer.close() + self._data = imagedrawer._data + self.filename = imagedrawer.filename + self.saveAnnotations() + + def saveAnnotations(self): + """ + Save annotated image to disk (image+instructions) + """ + filename = os.path.join(self.folder, 'deviceimage_annotations.json') + with open(filename, 'w') as fid: + json.dump(self._data, fid) + + def loadAnnotations(self) -> bool: + """ + Get the annotations. Only call this if the files exist + Need to load png/jpeg too + """ + json_filename = 'deviceimage_annotations.json' + raw_image_glob = 'deviceimage_raw.*' + json_full_filename = os.path.join(self.folder, json_filename) + json_found = os.path.exists(json_full_filename) + raw_files = glob.glob(os.path.join(self.folder, raw_image_glob)) + if raw_files: + self.filename = raw_files[0] + else: + self.filename = None + + if json_found and not self.filename: + raise RuntimeError("{} found but no {} found. Not sure how to " + "proceed, To continue either add raw image or " + "delete json " + "file".format(json_filename, raw_image_glob)) + elif not json_found: + return False + # this assumes there is only on of deviceimage_raw.* + with open(json_full_filename, 'r') as fid: + self._data = json.load(fid) + return True + + def updateValues(self, station, sweeptparameters=None): + """ + Update the data with actual voltages from the QDac + """ + for instrument, parameters in self._data.items(): + if instrument == 'font': + # skip font data + continue + for parameter in parameters.keys(): + value = station.components[instrument][parameter].get_latest() + try: + floatvalue = float(station.components[instrument][parameter].get_latest()) + if self._data[instrument][parameter].get('annotationformatter'): + valuestr = self._data[instrument][parameter].get('annotationformatter').format(floatvalue) + elif floatvalue > 1000 or floatvalue < 0.1: + valuestr = "{:.2e}".format(floatvalue) + else: + valuestr = "{:.2f}".format(floatvalue) + except (ValueError, TypeError): + valuestr = str(value) + self._data[instrument][parameter]['value'] = valuestr + if sweeptparameters: + for sweeptparameter in sweeptparameters: + if sweeptparameter._instrument.name == instrument and sweeptparameter.name == parameter: + self._data[instrument][parameter]['update'] = True + + + def makePNG(self, counter, path=None, title=None): + """ + Render the image with new voltage values and save it to disk + + Args: + counter (int): A counter for the experimental run number + """ + if self.filename is None: + raise ValueError('No image selected!') + if not qt.QApplication.instance(): + app = qt.QApplication(sys.argv) + else: + app = qt.QApplication.instance() + win = qt.QWidget() + grid = qt.QGridLayout() + win.setLayout(grid) + win.imageCanvas = qt.QLabel() + grid.addWidget(win.imageCanvas) + win.imageCanvas, pixmap = MakeDeviceImage._renderImage(self._data, + win.imageCanvas, + self.filename, + title) + filename = '{:03d}_deviceimage.png'.format(counter) + if path: + filename = os.path.join(path, filename) + pixmap.save(filename, 'png') diff --git a/qcodes/utils/wrappers.py b/qcodes/utils/wrappers.py new file mode 100644 index 00000000000..e8775257b48 --- /dev/null +++ b/qcodes/utils/wrappers.py @@ -0,0 +1,624 @@ +from os.path import abspath +from os.path import sep +from os import makedirs +import os +import logging +from copy import deepcopy +import numpy as np +from typing import Optional, Tuple +import matplotlib.pyplot as plt + +import qcodes as qc +from qcodes.loops import Loop +from qcodes.data.data_set import DataSet +from qcodes.plots.pyqtgraph import QtPlot +from qcodes.plots.qcmatplotlib import MatPlot +from qcodes.instrument.visa import VisaInstrument +from qcodes.utils.qcodes_device_annotator import DeviceImage + +from matplotlib import ticker +from IPython import get_ipython + +log = logging.getLogger(__name__) +CURRENT_EXPERIMENT = {} +CURRENT_EXPERIMENT["logging_enabled"] = False +pdfdisplay = {} + +def init(mainfolder: str, sample_name: str, station, plot_x_position=0.66, + annotate_image=True, display_pdf=True, display_individual_pdf=False): + """ + + Args: + mainfolder: base location for the data + sample_name: name of the sample + plot_x_position: fractional of screen position to put QT plots. + 0 is all the way to the left and + 1 is all the way to the right. + + """ + pdfdisplay['individual'] = display_individual_pdf + pdfdisplay['combined'] = display_pdf + if sep in sample_name: + raise TypeError("Use Relative names. That is without {}".format(sep)) + # always remove trailing sep in the main folder + if mainfolder[-1] == sep: + mainfolder = mainfolder[:-1] + + mainfolder = abspath(mainfolder) + + CURRENT_EXPERIMENT["mainfolder"] = mainfolder + CURRENT_EXPERIMENT["sample_name"] = sample_name + CURRENT_EXPERIMENT['init'] = True + + CURRENT_EXPERIMENT['plot_x_position'] = plot_x_position + + path_to_experiment_folder = sep.join([mainfolder, sample_name, ""]) + CURRENT_EXPERIMENT["exp_folder"] = path_to_experiment_folder + CURRENT_EXPERIMENT['pdf_subfolder'] = 'pdf' + try: + makedirs(path_to_experiment_folder) + except FileExistsError: + pass + try: + makedirs(sep.join([mainfolder, sample_name, + CURRENT_EXPERIMENT['pdf_subfolder']])) + except FileExistsError: + pass + log.info("experiment started at {}".format(path_to_experiment_folder)) + + loc_provider = qc.FormatLocation( + fmt=path_to_experiment_folder + '{counter}') + qc.data.data_set.DataSet.location_provider = loc_provider + CURRENT_EXPERIMENT["provider"] = loc_provider + + CURRENT_EXPERIMENT['station'] = station + + ipython = get_ipython() + # turn on logging only if in ipython + # else crash and burn + if ipython is None: + raise RuntimeWarning("History can't be saved. " + "-Refusing to proceed (use IPython/jupyter)") + else: + logfile = "{}{}".format(path_to_experiment_folder, "commands.log") + CURRENT_EXPERIMENT['logfile'] = logfile + if not CURRENT_EXPERIMENT["logging_enabled"]: + log.debug("Logging commands to: t{}".format(logfile)) + ipython.magic("%logstart -t -o {} {}".format(logfile, "append")) + CURRENT_EXPERIMENT["logging_enabled"] = True + else: + log.debug("Logging already started at {}".format(logfile)) + + # Annotate image if wanted and necessary + if annotate_image: + _init_device_image(station) + + +def _init_device_image(station): + + di = DeviceImage(CURRENT_EXPERIMENT["exp_folder"], station) + + success = di.loadAnnotations() + if not success: + di.annotateImage() + CURRENT_EXPERIMENT['device_image'] = di + + +def _select_plottables(tasks): + """ + Helper function to select plottable tasks. Used inside the doNd functions. + + A task is here understood to be anything that the qc.Loop 'each' can eat. + """ + # allow passing a single task + if not hasattr(tasks, '__iter__'): + tasks = (tasks,) + + # is the following check necessary AND sufficient? + plottables = [task for task in tasks if hasattr(task, '_instrument')] + + return tuple(plottables) + + +def _plot_setup(data, inst_meas, useQT=True, startranges=None): + title = "{} #{:03d}".format(CURRENT_EXPERIMENT["sample_name"], + data.location_provider.counter) + rasterized_note = " rasterized plot" + num_subplots = 0 + counter_two = 0 + for j, i in enumerate(inst_meas): + if getattr(i, "names", False): + num_subplots += len(i.names) + else: + num_subplots += 1 + if useQT: + plot = QtPlot(fig_x_position=CURRENT_EXPERIMENT['plot_x_position']) + else: + plot = MatPlot(subplots=(1, num_subplots)) + + def _create_plot(plot, i, name, data, counter_two, j, k): + """ + Args: + plot: The plot object, either QtPlot() or MatPlot() + i: The parameter to measure + name: - + data: The DataSet of the current measurement + counter_two: The sub-measurement counter. Each measurement has a + number and each sub-measurement has a counter. + j: The current sub-measurement + k: - + """ + color = 'C' + str(counter_two) + counter_two += 1 + inst_meas_name = "{}_{}".format(i._instrument.name, name) + inst_meas_data = getattr(data, inst_meas_name) + inst_meta_data = __get_plot_type(inst_meas_data, plot) + if useQT: + plot.add(inst_meas_data, subplot=j + k + 1) + plot.subplots[j + k].showGrid(True, True) + if j == 0: + plot.subplots[0].setTitle(title) + else: + plot.subplots[j + k].setTitle("") + + # Avoid SI rescaling if units are not standard units + standardunits = ['V', 's', 'J', 'W', 'm', 'eV', 'A', 'K', 'g', + 'Hz', 'rad', 'T', 'H', 'F', 'Pa', 'C', 'Ω', 'Ohm', + 'S'] + # make a dict mapping axis labels to axis positions + # TODO: will the labels for two axes ever be identical? + whatwhere = {} + for pos in ('bottom', 'left', 'right'): + whatwhere.update({plot.subplots[j+k].getAxis(pos).labelText: + pos}) + tdict = {'bottom': 'setXRange', 'left': 'setYRange'} + # now find the data (not setpoint) + checkstring = '{}_{}'.format(i._instrument.name, name) + + thedata = [data.arrays[d] for d in data.arrays.keys() + if d == checkstring][0] + + # Disable autoscale for the measured data + if thedata.unit not in standardunits: + subplot = plot.subplots[j+k] + try: + # 1D measurement + ax = subplot.getAxis(whatwhere[thedata.label]) + ax.enableAutoSIPrefix(False) + except KeyError: + # 2D measurement + # Then we should fetch the colorbar + ax = plot.traces[j+k]['plot_object']['hist'].axis + ax.enableAutoSIPrefix(False) + ax.setLabel(text=thedata.label, unit=thedata.unit, + unitPrefix='') + + # Set up axis scaling + for setarr in thedata.set_arrays: + subplot = plot.subplots[j+k] + ax = subplot.getAxis(whatwhere[setarr.label]) + # check for autoscaling + if setarr.unit not in standardunits: + ax.enableAutoSIPrefix(False) + # At this point, it has already been decided that + # the scale is milli whatever + # (the default empty plot is from -0.5 to 0.5) + # so we must undo that + ax.setScale(1e-3) + ax.setLabel(text=setarr.label, unit=setarr.unit, + unitPrefix='') + # set the axis ranges + if not(np.all(np.isnan(setarr))): + # In this case the setpoints are "baked" into the param + rangesetter = getattr(subplot.getViewBox(), + tdict[whatwhere[setarr.label]]) + rangesetter(setarr.min(), setarr.max()) + else: + # in this case someone must tell _create_plot what the + # range should be. We get it from startranges + rangesetter = getattr(subplot.getViewBox(), + tdict[whatwhere[setarr.label]]) + (rmin, rmax) = startranges[setarr.label] + rangesetter(rmin, rmax) + + else: + if 'z' in inst_meta_data: + xlen, ylen = inst_meta_data['z'].shape + rasterized = xlen*ylen > 5000 + plot.add(inst_meas_data, subplot=j + k + 1, + rasterized=rasterized) + else: + rasterized = False + plot.add(inst_meas_data, subplot=j + k + 1, color=color) + plot.subplots[j + k].grid() + if j == 0: + if rasterized: + fulltitle = title + rasterized_note + else: + fulltitle = title + plot.subplots[0].set_title(fulltitle) + else: + if rasterized: + fulltitle = rasterized_note + else: + fulltitle = "" + plot.subplots[j + k].set_title(fulltitle) + + for j, i in enumerate(inst_meas): + if getattr(i, "names", False): + # deal with multidimensional parameter + for k, name in enumerate(i.names): + _create_plot(plot, i, name, data, counter_two, j, k) + counter_two += 1 + else: + # simple_parameters + _create_plot(plot, i, i.name, data, counter_two, j, 0) + counter_two += 1 + return plot, num_subplots + + +def __get_plot_type(data, plot): + # this is a hack because expand_trace works + # in place. Also it should probably * expand its args and kwargs. N + # Same below + data_copy = deepcopy(data) + metadata = {} + plot.expand_trace((data_copy,), kwargs=metadata) + return metadata + + +def _save_individual_plots(data, inst_meas, display_plot=True): + + def _create_plot(i, name, data, counter_two, display_plot=True): + # Step the color on all subplots no just on plots + # within the same axis/subplot + # this is to match the qcodes-pyqtplot behaviour. + title = "{} #{:03d}".format(CURRENT_EXPERIMENT["sample_name"], + data.location_provider.counter) + rasterized_note = " rasterized plot full data available in datafile" + color = 'C' + str(counter_two) + counter_two += 1 + plot = MatPlot() + inst_meas_name = "{}_{}".format(i._instrument.name, name) + inst_meas_data = getattr(data, inst_meas_name) + inst_meta_data = __get_plot_type(inst_meas_data, plot) + if 'z' in inst_meta_data: + xlen, ylen = inst_meta_data['z'].shape + rasterized = xlen * ylen > 5000 + plot.add(inst_meas_data, rasterized=rasterized) + else: + rasterized = False + plot.add(inst_meas_data, color=color) + plot.subplots[0].grid() + if rasterized: + plot.subplots[0].set_title(title + rasterized_note) + else: + plot.subplots[0].set_title(title) + title_list = plot.get_default_title().split(sep) + title_list.insert(-1 , CURRENT_EXPERIMENT['pdf_subfolder']) + title = sep.join(title_list) + _rescale_mpl_axes(plot) + plot.tight_layout() + plot.save("{}_{:03d}.pdf".format(title, + counter_two)) + if display_plot: + plot.fig.canvas.draw() + plt.show() + else: + plt.close(plot.fig) + + counter_two = 0 + for j, i in enumerate(inst_meas): + if getattr(i, "names", False): + # deal with multidimensional parameter + for k, name in enumerate(i.names): + _create_plot(i, name, data, counter_two, display_plot) + counter_two += 1 + else: + _create_plot(i, i.name, data, counter_two, display_plot) + counter_two += 1 + + +def _flush_buffers(*params): + """ + If possible, flush the VISA buffer of the instrument of the + provided parameters. The params can be instruments as well. + + Supposed to be called inside doNd like so: + _flush_buffers(inst_set, *inst_meas) + """ + + for param in params: + if hasattr(param, '_instrument'): + inst = param._instrument + if hasattr(inst, 'visa_handle'): + status_code = inst.visa_handle.clear() + if status_code is not None: + log.warning("Cleared visa buffer on " + "{} with status code {}".format(inst.name, + status_code)) + elif isinstance(param, VisaInstrument): + inst = param + status_code = inst.visa_handle.clear() + if status_code is not None: + log.warning("Cleared visa buffer on " + "{} with status code {}".format(inst.name, + status_code)) + + +def save_device_image(sweeptparameters): + counter = CURRENT_EXPERIMENT['provider'].counter + title = "{} #{:03d}".format(CURRENT_EXPERIMENT["sample_name"], counter) + di = CURRENT_EXPERIMENT['device_image'] + di.updateValues(CURRENT_EXPERIMENT['station'], sweeptparameters) + + log.debug(os.path.join(CURRENT_EXPERIMENT["exp_folder"], + '{:03d}'.format(counter))) + + di.makePNG(CURRENT_EXPERIMENT["provider"].counter, + os.path.join(CURRENT_EXPERIMENT["exp_folder"], + '{:03d}'.format(counter)), title) + + +def _do_measurement(loop: Loop, set_params: tuple, meas_params: tuple, + do_plots: Optional[bool]=True) -> Tuple[QtPlot, DataSet]: + """ + The function to handle all the auxiliary magic of the T10 users, e.g. + their plotting specifications, the device image annotation etc. + The input loop should just be a loop ready to run and perform the desired + measurement. The two params tuple are used for plotting. + + Args: + loop: The QCoDeS loop object describing the actual measurement + set_params: tuple of tuples. Each tuple is of the form + (param, start, stop) + meas_params: tuple of parameters to measure + + Returns: + (plot, data) + """ + parameters = [sp[0] for sp in set_params] + list(meas_params) + _flush_buffers(*parameters) + + # startranges for _plot_setup + startranges = dict(zip((sp[0].label for sp in set_params), + ((sp[1], sp[2]) for sp in set_params))) + + interrupted = False + + data = loop.get_data_set() + + if do_plots: + plot, _ = _plot_setup(data, meas_params, startranges=startranges) + else: + plot = None + try: + if do_plots: + _ = loop.with_bg_task(plot.update).run() + else: + _ = loop.run() + except KeyboardInterrupt: + interrupted = True + print("Measurement Interrupted") + if do_plots: + # Ensure the correct scaling before saving + for subplot in plot.subplots: + vBox = subplot.getViewBox() + vBox.enableAutoRange(vBox.XYAxes) + cmap = None + # resize histogram + for trace in plot.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'] + max = trace['config']['z'].max() + min = trace['config']['z'].min() + trace['plot_object']['hist'].setLevels(min, max) + trace['plot_object']['hist'].vb.autoRange() + if cmap: + plot.set_cmap(cmap) + # set window back to original size + plot.win.resize(1000, 600) + plot.save() + plt.ioff() + pdfplot, num_subplots = _plot_setup(data, meas_params, useQT=False) + # pad a bit more to prevent overlap between + # suptitle and title + _rescale_mpl_axes(pdfplot) + pdfplot.fig.tight_layout(pad=3) + title_list = plot.get_default_title().split(sep) + title_list.insert(-1 , CURRENT_EXPERIMENT['pdf_subfolder']) + title = sep.join(title_list) + + + pdfplot.save("{}.pdf".format(title)) + if pdfdisplay['combined'] or (num_subplots == 1 and pdfdisplay['individual']): + pdfplot.fig.canvas.draw() + plt.show() + else: + plt.close(pdfplot.fig) + if num_subplots > 1: + _save_individual_plots(data, meas_params, pdfdisplay['individual']) + plt.ion() + if CURRENT_EXPERIMENT.get('device_image'): + log.debug('Saving device image') + save_device_image(tuple(sp[0] for sp in set_params)) + + # add the measurement ID to the logfile + with open(CURRENT_EXPERIMENT['logfile'], 'a') as fid: + print("#[QCoDeS]# Saved dataset to: {}".format(data.location), + file=fid) + if interrupted: + raise KeyboardInterrupt + return plot, data + +def _rescale_mpl_axes(plot): + for i, subplot in enumerate(plot.subplots): + for axis in 'x', 'y': + unit = plot.traces[i]['config'][axis].unit + label = plot.traces[i]['config'][axis].label + maxval = abs(plot.traces[0]['config'][axis].ndarray).max() + if unit == 'V' and maxval < 0.1: + unit = 'mV' + tx = ticker.FuncFormatter(lambda i, pos: '{0:g}'.format(i*1e3)) + getattr(subplot, "{}axis".format(axis)).set_major_formatter(tx) + getattr(subplot, "set_{}label".format(axis))("{} ({})".format(label, unit)) + +def do1d(inst_set, start, stop, num_points, delay, *inst_meas, do_plots=True): + """ + + Args: + inst_set: Instrument to sweep over + start: Start of sweep + stop: End of sweep + num_points: Number of steps to perform + delay: Delay at every step + *inst_meas: any number of instrument to measure and/or tasks to + perform at each step of the sweep + do_plots: Default True: If False no plots are produced. + Data is still saved + and can be displayed with show_num. + + Returns: + plot, data : returns the plot and the dataset + + """ + + loop = qc.Loop(inst_set.sweep(start, + stop, + num=num_points), delay).each(*inst_meas) + + set_params = (inst_set, start, stop), + meas_params = _select_plottables(inst_meas) + + plot, data = _do_measurement(loop, set_params, meas_params, + do_plots=do_plots) + + return plot, data + + +def do1dDiagonal(inst_set, inst2_set, start, stop, num_points, + delay, start2, slope, *inst_meas, do_plots=True): + """ + Perform diagonal sweep in 1 dimension, given two instruments + + Args: + inst_set: Instrument to sweep over + inst2_set: Second instrument to sweep over + start: Start of sweep + stop: End of sweep + num_points: Number of steps to perform + delay: Delay at every step + start2: Second start point + slope: slope of the diagonal cut + *inst_meas: any number of instrument to measure + do_plots: Default True: If False no plots are produced. + Data is still saved and can be displayed with show_num. + + Returns: + plot, data : returns the plot and the dataset + + """ + + # (WilliamHPNielsen) If I understand do1dDiagonal correctly, the inst2_set + # is supposed to be varied secretly in the background + set_params = ((inst_set, start, stop),) + meas_params = _select_plottables(inst_meas) + + slope_task = qc.Task(inst2_set, (inst_set)*slope+(slope*start-start2)) + + loop = qc.Loop(inst_set.sweep(start, stop, num=num_points), + delay).each(slope_task, *inst_meas, inst2_set) + + plot, data = _do_measurement(loop, set_params, meas_params, + do_plots=do_plots) + + return plot, data + + +def do2d(inst_set, start, stop, num_points, delay, + inst_set2, start2, stop2, num_points2, delay2, + *inst_meas, do_plots=True): + """ + + Args: + inst_set: Instrument to sweep over + start: Start of sweep + stop: End of sweep + num_points: Number of steps to perform + delay: Delay at every step + inst_set2: Second instrument to sweep over + start2: Start of sweep for second instrument + stop2: End of sweep for second instrument + num_points2: Number of steps to perform + delay2: Delay at every step for second instrument + *inst_meas: + do_plots: Default True: If False no plots are produced. + Data is still saved and can be displayed with show_num. + + Returns: + plot, data : returns the plot and the dataset + + """ + + for inst in inst_meas: + if getattr(inst, "setpoints", False): + raise ValueError("3d plotting is not supported") + + innerloop = qc.Loop(inst_set2.sweep(start2, + stop2, + num=num_points2), + delay2).each(*inst_meas) + outerloop = qc.Loop(inst_set.sweep(start, + stop, + num=num_points), + delay).each(innerloop) + + set_params = ((inst_set, start, stop), + (inst_set2, start2, stop2)) + meas_params = _select_plottables(inst_meas) + + plot, data = _do_measurement(outerloop, set_params, meas_params, + do_plots=do_plots) + + return plot, data + + +def show_num(id, useQT=False): + """ + Show and return plot and data for id in current instrument. + Args: + id(number): id of instrument + + Returns: + plot, data : returns the plot and the dataset + + """ + if not getattr(CURRENT_EXPERIMENT, "init", True): + raise RuntimeError("Experiment not initalized. " + "use qc.Init(mainfolder, samplename)") + + str_id = '{0:03d}'.format(id) + + t = qc.DataSet.location_provider.fmt.format(counter=str_id) + data = qc.load_data(t) + + plots = [] + for value in data.arrays.keys(): + if "set" not in value: + if useQT: + plot = QtPlot(getattr(data, value), + fig_x_position=CURRENT_EXPERIMENT['plot_x_position']) + title = "{} #{}".format(CURRENT_EXPERIMENT["sample_name"], + str_id) + plot.subplots[0].setTitle(title) + plot.subplots[0].showGrid(True, True) + else: + plot = MatPlot(getattr(data, value)) + title = "{} #{}".format(CURRENT_EXPERIMENT["sample_name"], + str_id) + plot.subplots[0].set_title(title) + plot.subplots[0].grid() + plots.append(plot) + return data, plots diff --git a/requirements.txt b/requirements.txt index 32d5add6fb3..bbb6bc52be7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ numpy==1.13.1 matplotlib==2.0.2 +mplcursors==0.1 pyqtgraph==0.10.0 PyVISA==1.8 PyQt5==5.9 diff --git a/setup.py b/setup.py index 2aafa475040..23e43f5fe28 100644 --- a/setup.py +++ b/setup.py @@ -55,11 +55,16 @@ def readme(): # if we want to install without tests: # packages=find_packages(exclude=["*.tests", "tests"]), packages=find_packages(), - package_data={'qcodes': ['widgets/*.js', 'widgets/*.css', 'config/*.json']}, - install_requires= [ + package_data={'qcodes': ['monitor/dist/*', 'monitor/dist/js/*', + 'monitor/dist/css/*', 'config/*.json']}, + install_requires=[ 'numpy>=1.10', 'pyvisa>=1.8', - 'h5py>=2.6' + 'ipython>=4.1.0', + 'ipykernel!=4.6.0', # https://github.com/ipython/ipykernel/issues/240 in 4.6 + 'jupyter>=1.0.0', + 'h5py>=2.6', + 'websockets>=3.2' ], test_suite='qcodes.tests',