Source code for qtt.algorithms.awg_to_plunger

""" Functionality to determine the awg_to_plunger ratio.

pieter.eendebak@tno.nl

"""

import copy
from typing import Optional, Tuple

import matplotlib.pyplot as plt
import numpy as np
from qcodes.plots.qcmatplotlib import MatPlot

import qtt.algorithms.images
import qtt.pgeometry
import qtt.utilities.imagetools
from qtt.algorithms.images import straightenImage
from qtt.data import get_dataset, plot_dataset
from qtt.measurements.scans import scan2Dfast, scanjob_t
from qtt.utilities.imagetools import cleanSensingImage

# %% Helper functions


[docs]def click_line(fig: Optional[int], show_points: bool = False) -> Tuple[float, float]: """ Define a line through two points. The points are choosen by clicking on a position in a plot, two times. Args: fig: number of figure to plot the points in, when None it will plot a figure. showpoints: If True, then plot the selected points in the figure Returns: offset (float): offset of the line. slope (float): slope of the line. """ if fig is not None: plt.figure(fig) pts0, pts1 = plt.ginput(2) if (pts1[0] - pts0[0]) == 0: raise Exception('vertical line not implemented') slope = (pts1[1] - pts0[1]) / (pts1[0] - pts0[0]) offset = (pts0[1] - slope * pts0[0]) if show_points: pts = np.array([pts0, pts1]).T plt.plot(pts[0], pts[1], '.-g') return offset, slope
# %% Main functions
[docs]def measure_awg_to_plunger(station, gate, minstrument, scanrange=30, step=0.5): """ Performing a scan2Dfast measurement, same gate on both axis, where the one axis is sweeped with the awg and one axis is stepped with the DAC's. Measurement should be centred around an addition line. From the slope of the addition line the awg to plunger conversion factor can be checked with the function analyse_awg_to_plunger. Args: station (QCoDeS station): measurement setup. gate (str): gate for which the awg to plunger conversion. minstrument (str, int): list with the name of the measurement instrument (str), and the channel number (int). scanrange (float): sweep- and steprange (mV), making a square 2d measurement. step (float): stepsize (mV). Returns: result (dict): resultresult (dic): result dictionary of the function measure_awg_to_plunger, shape: result = {'type': 'awg_to_plunger', 'awg_to_plunger': None, 'dataset': ds.location}. """ gates = station.gates value0 = gates.get(gate) scanjob = scanjob_t({'sweepdata': {'param': gate, 'range': scanrange}, 'stepdata': { 'param': gate, 'range': scanrange, 'step': step, 'start': value0 - scanrange / 2}}) scanjob['minstrument'] = minstrument scanjob['minstrumenthandle'] = minstrument scanjob['Naverage'] = 500 scanjob.setWaitTimes(station) scanjob['wait_time_startscan'] += .5 ds = scan2Dfast(station, scanjob) result = {'type': 'awg_to_plunger', 'awg_to_plunger': None, 'dataset': ds.location} return result
[docs]def analyse_awg_to_plunger(result: dict, method: str = 'hough', fig: Optional[int] = None) -> dict: """ Determine the awg_to_plunger conversion factor from a 2D scan, two possible methods: 'hough' it fits the slope of the addition line and calculates the correction to the awg_to_plunger conversion factor from there. if this doesn't work for some reason, method 'click' can be used to find the addition lines by hand/eye. Args: result: result dictionary of the function measure_awg_to_plunger, shape: result = {'type': 'awg_to_plunger', 'awg_to_plunger': None, 'dataset': ds.location}. method: either 'hough' or 'click'. fig : determines of the analysis staps and the result is plotted. Returns: result: including to following entries: angle (float): angle in radians. angle_degrees (float): angle in degrees. correction of awg_to_plunger (float): correction factor. dataset (str): location where the dataset is stored. type(str): type of calibration, 'awg_to_plunger'. """ # getting the dataset from the result from the measure_awg_to_plunger function if result.get('type') != 'awg_to_plunger': raise AssertionError('not of type awg_to_plunger!') ds = get_dataset(result) # choosing a method; # the method 'hough' fits the addition line if method == 'hough': import cv2 im, tr = qtt.data.dataset2image(ds) imextent = tr.scan_image_extent() istep = tr.scan_resolution() _, r = qtt.algorithms.images.straightenImage( im, imextent, mvx=istep, mvy=None) H = r[4] imc = cleanSensingImage(im, sigma=0.93, dy=0) imx, _ = straightenImage(imc, imextent, mvx=istep, verbose=0) imx = imx.astype(np.float64) * \ (100. / np.percentile(imx, 99)) # scale image gray = qtt.pgeometry.scaleImage(imx) edges = cv2.Canny(gray, 50, 150, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, int(gray.shape[0] * .5)) if lines is None: angle_pixel = None angle = None xscan = None angle_deg = None correction = None else: angles = lines[:, 0, 1] angle_pixel = angles[0] # take most voted line fac = 2 xpix = np.array([[0, 0], [-fac * np.sin(angle_pixel), fac * np.cos(angle_pixel)]]).T tmp = qtt.pgeometry.projectiveTransformation(np.linalg.inv(H), xpix) xscan = tr.pixel2scan(tmp) def vec2angle(v): return np.arctan2(v[0], v[1]) angle = vec2angle(xscan[:, 1] - xscan[:, 0]) correction = -1 / np.tan(angle) angle_deg = np.rad2deg(angle) # plotting the analysis steps of the data if fig is not None: plt.figure(fig + 1) plt.clf() plt.imshow(gray) plt.axis('image') if angle_pixel is not None: for offset_in_pixels in [-40, -20, 0, 20, 40]: label = None if offset_in_pixels == 0: label = 'detected angle' qtt.pgeometry.plot2Dline( [np.cos(angle_pixel), np.sin(angle_pixel), offset_in_pixels], 'm', label=label) if angle is not None: plt.title(f'Detected line direction: angle {angle_deg:.2f} [deg]') plt.figure(fig + 2) plt.clf() plt.imshow(edges) plt.axis('image') plt.title('Detected edge points') # the method click relies on the user clicking two points to indicate the addition line elif method == 'click': if fig is not None: plt.figure(fig) plt.clf() MatPlot(ds.default_parameter_array(), num=fig) plt.draw() plt.pause(1e-3) print("Please click two different points on the addition line") offset, slope = click_line(fig=fig) angle_pixel = None angle = -np.pi / 2 - np.tanh(slope) correction = -1 / np.tan(angle) angle_deg = angle / (2 * np.pi) * 360 else: raise Exception('method %s not implemented' % (method,)) # filling the result dictionary result = copy.copy(result) result['_angle_pixel'] = angle_pixel result['angle'] = angle result['angle_degrees'] = angle_deg result['correction of awg_to_plunger'] = correction if method == 'click': result['slope'] = slope result['offset'] = offset # optional, plotting figures showing the analysis if fig is not None: plot_awg_to_plunger(result=result, fig=fig) return result
[docs]def plot_awg_to_plunger(result, fig=10): """ This function tests the analyse_awg_to_plunger function. Plotting is optional and for debugging purposes. Args: result (dict): result dictionary from the analyse_awg_to_plunger function. fig (int): index of matplotlib window. """ if not result.get('type', None) == 'awg_to_plunger': raise Exception('calibration result not of correct type ') angle = result['angle'] ds = get_dataset(result) v = ds.default_parameter_array() dy = v.set_arrays[0][-1]-v.set_arrays[0][0] centre = [np.mean(v.set_arrays[1]), np.mean(v.set_arrays[0])] plot_dataset(ds, fig=fig) if angle is not None: rho = -(centre[0] * np.cos(angle) - np.sin(angle) * centre[1]) for offset in dy*np.linspace(-1, 1, 7): label = None if offset == 0: label = 'detected angle' qtt.pgeometry.plot2Dline( [np.cos(angle), -np.sin(angle), rho + offset], '--m', alpha=.6, label=label) angle_deg = np.rad2deg(angle) plt.title(f'Detected line direction: angle {angle_deg:.2f} [deg]')