diff --git a/.gitignore b/.gitignore index 840a47c0037..a77a2c6c4c1 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ coverage.xml *.mo *.pot +# MAC OSX junk files +.DS_Store + # Django stuff: *.log diff --git a/qcodes/__init__.py b/qcodes/__init__.py index 859bcaf45dd..307d1ac7b1d 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -34,6 +34,7 @@ 'try "from qcodes.plots.pyqtgraph import QtPlot" ' 'to see the full error') +from qcodes.utils.wrappers import do1d, do2d, do1dDiagonal, show_num, init # only import in name space if the gui is set to noebook # and there is multiprocessing if config['gui']['notebook'] and config['core']['legacy_mp']: 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/ZI/ZIUHFLI.py b/qcodes/instrument_drivers/ZI/ZIUHFLI.py index 1f78667d953..6abd0186f38 100644 --- a/qcodes/instrument_drivers/ZI/ZIUHFLI.py +++ b/qcodes/instrument_drivers/ZI/ZIUHFLI.py @@ -379,10 +379,20 @@ def get(self): # We add one second to account for latencies and random delays meas_time = segs*(params['scope_duration'].get()+deadtime)+1 npts = params['scope_length'].get() - + # one shot per trigger. This needs to be set every time + # a the scope is enabled as below using scope_runstop + # We should also test if scopeModule/mode and scopeModule/averager/weight + # needs to be set every time since we are creating a new scopemodule + # here + self._instrument.daq.setInt('/{}/scopes/0/single'.format(self._instrument.device), 1) + self._instrument.daq.sync() # Create a new scopeModule instance (TODO: Why a new instance?) scope = self._instrument.daq.scopeModule() - + # TODO We are hard coding scope mode and avg weight here because the setting + # in the main driver references a different scope which will fail and give garbage data + # YOU cannot set other scope modes or weights at the moment + scope.set('scopeModule/mode', 1) + scope.set('scopeModule/averager/weight', 1) # Subscribe to the relevant... publisher? scope.subscribe('/{}/scopes/0/wave'.format(self._instrument.device)) @@ -390,6 +400,7 @@ def get(self): params['scope_runstop'].set('run') log.info('[*] Starting ZI scope acquisition.') + # Start something... hauling data from the scopeModule? scope.execute() diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index a2d541008f5..06dc9722608 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -4,6 +4,7 @@ import numpy as np import pyqtgraph as pg import pyqtgraph.multiprocess as pgmp +from pyqtgraph import QtGui # note that pyqtgraph still uses the old pyqt4 layout import warnings from collections import namedtuple @@ -27,6 +28,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 @@ -39,7 +44,8 @@ class QtPlot(BasePlot): rpg = None 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(): @@ -58,6 +64,10 @@ def __init__(self, *args, figsize=(1000, 600), interval=0.25, self.win = self.rpg.GraphicsWindow(title=window_title) 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: diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index 7f0aa1fb06f..685f4604e5c 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -6,6 +6,7 @@ import matplotlib.pyplot as plt from matplotlib.transforms import Bbox +from matplotlib import cm import numpy as np from numpy.ma import masked_invalid, getmask @@ -226,6 +227,14 @@ def _draw_pcolormesh(self, ax, z, x=None, y=None, subplot=1, # if any entire array is masked, don't draw at all # there's nothing to draw, and anyway it throws a warning return False + if 'cmap' not in kwargs: + kwargs['cmap'] = cm.hot + if 'edgecolors' not in kwargs: + # Matplotlib pcolormesh per default are drawn as individual patches lined up next to each other + # due to rounding this produces visible gaps in some pdf viewers. To prevent this we draw each + # mesh with a visible edge (slightly overlapping) this assumes alpha=1 or it will produce artifacts + # at the overlaps + kwargs['edgecolors'] = 'face' pc = ax.pcolormesh(*args, **kwargs) if getattr(ax, 'qcodes_colorbar', None): diff --git a/qcodes/tests/instrument_mocks.py b/qcodes/tests/instrument_mocks.py index 3163d2e95b6..255eb7e0df3 100644 --- a/qcodes/tests/instrument_mocks.py +++ b/qcodes/tests/instrument_mocks.py @@ -297,7 +297,7 @@ def __init__(self, name='dummy', gates=['dac1', 'dac2', 'dac3'], **kwargs): self.add_parameter(g, parameter_class=ManualParameter, initial_value=0, - label='Gate {} (arb. units)'.format(g), + label='Gate {} (arbUnit)'.format(g), vals=Numbers(-800, 400)) diff --git a/qcodes/utils/wrappers.py b/qcodes/utils/wrappers.py new file mode 100644 index 00000000000..3e40d3ddd68 --- /dev/null +++ b/qcodes/utils/wrappers.py @@ -0,0 +1,289 @@ +import qcodes as qc +from os.path import abspath +from os.path import sep +from os import makedirs +import logging + +from qcodes.plots.pyqtgraph import QtPlot +from qcodes.plots.qcmatplotlib import MatPlot +from IPython import get_ipython + +CURRENT_EXPERIMENT = {} +CURRENT_EXPERIMENT["logging_enabled"] = False + + +def init(mainfolder:str, sample_name: str, plot_x_position=0.66): + """ + + 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. + + """ + if sep in sample_name: + raise TypeError("Use Relative names. That is wihtout {}".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 + + try: + makedirs(path_to_experiment_folder) + except FileExistsError: + pass + + logging.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 + + 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") + if not CURRENT_EXPERIMENT["logging_enabled"]: + logging.debug("Logging commands to: t{}".format(logfile)) + ipython.magic("%logstart -t {} {}".format(logfile, "append")) + CURRENT_EXPERIMENT["logging_enabled"] = True + else: + logging.debug("Logging already started at {}".format(logfile)) + + +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 isinstance(tasks, tuple): + 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): + title = "{} #{:03d}".format(CURRENT_EXPERIMENT["sample_name"], + data.location_provider.counter) + if useQT: + plot = QtPlot(fig_x_position=CURRENT_EXPERIMENT['plot_x_position']) + else: + plot = MatPlot() + for j, i in enumerate(inst_meas): + if getattr(i, "names", False): + # deal with multidimensional parameter + for k, name in enumerate(i.names): + inst_meas_name = "{}_{}".format(i._instrument.name, name) + plot.add(getattr(data, inst_meas_name), subplot=j + k + 1) + if useQT: + plot.subplots[j+k].showGrid(True, True) + if j == 0: + plot.subplots[0].setTitle(title) + else: + plot.subplots[j+k].setTitle("") + else: + plot.subplots[j+k].grid() + if j == 0: + plot.subplots[0].set_title(title) + else: + plot.subplots[j+k].set_title("") + else: + # simple_parameters + inst_meas_name = "{}_{}".format(i._instrument.name, i.name) + plot.add(getattr(data, inst_meas_name), subplot=j + 1) + if useQT: + plot.subplots[j].showGrid(True, True) + if j == 0: + plot.subplots[0].setTitle(title) + else: + plot.subplots[j].setTitle("") + else: + plot.subplots[j].grid() + if j == 0: + plot.subplots[0].set_title(title) + else: + plot.subplots[j].set_title("") + return plot + + +def _save_individual_plots(data, inst_meas): + title = "{} #{:03d}".format(CURRENT_EXPERIMENT["sample_name"], data.location_provider.counter) + 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): + counter_two += 1 + plot = MatPlot() + inst_meas_name = "{}_{}".format(i._instrument.name, name) + plot.add(getattr(data, inst_meas_name)) + plot.subplots[0].set_title(title) + plot.subplots[0].grid() + plot.save("{}_{:03d}.pdf".format(plot.get_default_title(), counter_two)) + else: + counter_two += 1 + plot = MatPlot() + # simple_parameters + inst_meas_name = "{}_{}".format(i._instrument.name, i.name) + plot.add(getattr(data, inst_meas_name)) + plot.subplots[0].set_title(title) + plot.subplots[0].grid() + plot.save("{}_{:03d}.pdf".format(plot.get_default_title(), counter_two)) + + +def do1d(inst_set, start, stop, division, delay, *inst_meas): + """ + + Args: + inst_set: Instrument to sweep over + start: Start of sweep + stop: End of sweep + division: Spacing between values + delay: Delay at every step + *inst_meas: any number of instrument to measure and/or tasks to + perform at each step of the sweep + + Returns: + plot, data : returns the plot and the dataset + + """ + loop = qc.Loop(inst_set.sweep(start, + stop, division), delay).each(*inst_meas) + data = loop.get_data_set() + plottables = _select_plottables(inst_meas) + plot = _plot_setup(data, plottables) + try: + _ = loop.with_bg_task(plot.update, plot.save).run() + except KeyboardInterrupt: + print("Measurement Interrupted") + _save_individual_plots(data, plottables) + return plot, data + + +def do1dDiagonal(inst_set, inst2_set, start, stop, division, delay, start2, slope, *inst_meas): + """ + 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 + division: Spacing between values + delay: Delay at every step + start2: Second start point + slope: slope of the diagonal cut + *inst_meas: any number of instrument to measure + + Returns: + plot, data : returns the plot and the dataset + + """ + loop = qc.Loop(inst_set.sweep(start, stop, division), delay).each( + qc.Task(inst2_set, (inst_set) * slope + (slope * start - start2)), *inst_meas, inst2_set) + data = loop.get_data_set() + plottables = _select_plottables(inst_meas) + plot = _plot_setup(data, plottables) + try: + _ = loop.with_bg_task(plot.update, plot.save).run() + except KeyboardInterrupt: + print("Measurement Interrupted") + _save_individual_plots(data, plottables) + return plot, data + + +def do2d(inst_set, start, stop, division, delay, inst_set2, start2, stop2, division2, delay2, *inst_meas): + """ + + Args: + inst_set: Instrument to sweep over + start: Start of sweep + stop: End of sweep + division: Spacing between values + delay: Delay at every step + inst_set_2: Second instrument to sweep over + start_2: Start of sweep for second instrument + stop_2: End of sweep for second instrument + division_2: Spacing between values for second instrument + delay_2: Delay at every step for second instrument + *inst_meas: + + 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") + + loop = qc.Loop(inst_set.sweep(start, stop, division), delay).loop(inst_set2.sweep(start2,stop2,division2), delay2).each( + *inst_meas) + data = loop.get_data_set() + plottables = _select_plottables(inst_meas) + plot = _plot_setup(data, plottables) + try: + _ = loop.with_bg_task(plot.update, plot.save).run() + except KeyboardInterrupt: + print("Measurement Interrupted") + _save_individual_plots(data, plottables) + 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