Source code for plots

# This file is part of the scanning-squid package.
#
# Copyright (c) 2018 Logan Bishop-Van Horn
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.colors as colors
import numpy as np
from utils import make_scan_vectors, make_scan_grids, moving_avg, to_real_units, clear_artists
from typing import Dict, List, Optional, Sequence, Any, Union, Tuple
import warnings
warnings.filterwarnings('ignore', message='The unit of the quantity is stripped.')

[docs]class ScanPlot(object): """Plot displaying acquired images in all measurement channels, updated live during a scan. """ def __init__(self, scan_params: Dict[str, Any], ureg: Any, **kwargs) -> None: """ Args: scan_params: Scan parameters as defined in measurement configuration file. prefactors: Dict of pint quantities defining conversion factor from DAQ voltage to real units for each measurement channel. ureg: pint UnitRegistry, manages units. """ self.scan_params = scan_params self.ureg = ureg self.Q_ = ureg.Quantity self.channels = scan_params['channels'] self.fast_ax = scan_params['fast_ax'] self.slow_ax = 'y' if self.fast_ax == 'x' else 'x' self.line_colors = ['#d80202' ,'#545454' ,'#777777' ,'#a8a8a8', '#d1d1d1'] MAXN_COLS = 4 N = len(self.channels.keys()) cols = N if N < MAXN_COLS else MAXN_COLS plot_rows = int(np.ceil(N / cols)) rows = 3 * plot_rows self.fig, self.ax = plt.subplots(rows, cols, figsize=(10,4.5 * plot_rows), gridspec_kw={"height_ratios":[0.075, 1, 0.5]*plot_rows}) self.fig.patch.set_alpha(1) self.plots = {'colorbars': {}, 'images': {}, 'lines': {}} for i, ch in enumerate(self.channels.keys()): self.plots['colorbars'].update({ch: {'cax': self.ax[0][i]}}) self.plots['images'].update({ch: {'ax' :self.ax[1][i]}}) self.plots['lines'].update({ch: self.ax[2][i]}) for ch, ax in self.plots['images'].items(): ax['ax'].set_aspect('equal') ax['ax'].set_xlabel('x position [V]') ax['ax'].set_ylabel('y position [V]') for ch, ax in self.plots['lines'].items(): ax.grid(True) ax.set_aspect('auto') ax.set_xlabel('{} position [V]'.format(self.fast_ax)) self.init_empty()
[docs] def init_empty(self): """Initialize the plot with all images empty. They will be filled during the scan. """ self.scan_vectors = make_scan_vectors(self.scan_params, self.ureg) self.X, self.Y = np.meshgrid(self.scan_vectors['x'], self.scan_vectors['y']) empty = np.full_like(self.X, np.nan, dtype=np.double) for ch in self.channels: im = self.plots['images'][ch]['ax'].pcolormesh(self.X, self.Y, empty) self.plots['images'][ch].update({'quad': im}) cbar = plt.colorbar(im, cax=self.plots['colorbars'][ch]['cax'], orientation='horizontal') self.plots['colorbars'][ch]['cax'].set_label(r'{}'.format(self.channels[ch]['unit_latex'])) self.plots['colorbars'][ch].update({'cbar': cbar}) for ax, ch in zip(self.ax[0], self.channels.keys()): ax.set_title(self.channels[ch]['label']) self.fig.canvas.draw() self.fig.tight_layout()
[docs] def update(self, data_set: Any, loop_counter: Any, num_lines: Optional[int]=5, offline: Optional[bool]=False) -> None: """Update the plot with updated DataSet. Called after each line of the scan. Args: DataSet: active data set, with a new line of data added with each loop iteration. loop_counter: utils.Counter instance, lets us know where we are in the scan. num_lines: Number of previous linecuts to plot, including the line just scanned. Currently can only handle num_lines <= 5. offline: False if this is being called during a scan. """ self.location = data_set.location self.fig.suptitle(self.location, x=0.5, y=1, fontsize=10) data = to_real_units(data_set) meta = data_set.metadata['loop']['metadata'] slow_ax = 'x' if meta['fast_ax'] == 'y' else 'y' line = loop_counter.count if not offline else meta['scan_size'][slow_ax] - 1 for idx, ch in enumerate(self.channels): data_ch = data[:,idx,:] if self.fast_ax.lower() == 'y': data_ch = data_ch.T # clear_artists(self.plots['images'][ch]['ax']) # clear_artists(self.plots['lines'][ch]) norm = colors.Normalize().autoscale(np.ma.masked_invalid(data_ch)) self.plots['images'][ch]['ax'].clear() self.plots['images'][ch]['ax'].set_xlabel('x position [V]') self.plots['images'][ch]['ax'].set_ylabel('y position [V]') self.plots['images'][ch]['quad'] = self.plots['images'][ch]['ax'].pcolormesh( self.X, self.Y, np.ma.masked_invalid(data_ch), norm=norm) self.plots['colorbars'][ch]['cax'].clear() self.plots['colorbars'][ch]['cbar'] = self.fig.colorbar(self.plots['images'][ch]['quad'], cax=self.plots['colorbars'][ch]['cax'], orientation='horizontal') self.plots['colorbars'][ch]['cbar'].locator = ticker.MaxNLocator(nbins=3) self.plots['colorbars'][ch]['cbar'].update_ticks() self.plots['colorbars'][ch]['cbar'].set_label(r'{}'.format(self.channels[ch]['unit_latex'])) self.plots['colorbars'][ch]['cbar'].update_normal(self.plots['images'][ch]['quad']) # self.plots['images'][ch]['ax'].relim() self.plots['lines'][ch].relim() self.plots['colorbars'][ch]['cax'].minorticks_on() #: Update linecuts self.plots['lines'][ch].clear() self.plots['lines'][ch].grid(True) self.plots['lines'][ch].set_aspect('auto') self.plots['lines'][ch].set_xlabel('{} position [V]'.format(self.fast_ax)) self.plots['lines'][ch].set_ylabel(r'{}'.format(self.channels[ch]['unit_latex'])) xdata = self.scan_vectors[self.fast_ax] if line < num_lines: for l in range(line+1): ydata = data_ch[:,l] if self.fast_ax == 'y' else data_ch[l,:] self.plots['lines'][ch].plot(xdata, ydata, lw=2, color=self.line_colors[line-l]) else: for l in range(num_lines): ydata = data_ch[:,line-num_lines+l+1] if self.fast_ax == 'y' else data_ch[line-num_lines+l+1,:] self.plots['lines'][ch].plot(xdata, ydata, lw=2, color=self.line_colors[num_lines-l-1]) self.fig.canvas.draw()
[docs] def save(self, fname=None): """Save plot to png file. Args: fname: File to which to save the plot. If fname is None, saves to data location as {scan_params['fname']}.png """ if fname is None: fname = os.path.join(self.location, self.scan_params['fname'] + '.png') plt.savefig(fname, dpi=300)
class ScanPlotFromDataSet(ScanPlot): """Generate ScanPlot instance from a completed DataSet rather than during a Loop. """ def __init__(self, scan_data: Any, ureg: Optional[Any]=None) -> None: """ Args: scan_data: DataSet to plot, as created by microscope.scan_plane ureg: pint UnitRegistry, manages units. """ if ureg is None: from pint import UnitRegistry ureg = UnitRegistry() #: Tell the UnitRegistry what a Phi0 is, and that ohm = Ohm with open('squid_units.txt', 'w') as f: f.write('Phi0 = 2.067833831e-15 * Wb\n') f.write('Ohm = ohm\n') ureg.load_definitions('./squid_units.txt') meta = scan_data.metadata['loop']['metadata'] super().__init__(meta, meta['prefactors'], ureg) self.update(scan_data, None, offline=True)
[docs]class TDCPlot(object): """Plot displaying capacitance as a function of z voltage, updated live during a scan. """ def __init__(self, tdc_params: Dict[str, Any], ureg: Any) -> None: """ Args: tdc_params: Touchdown parameters as defined in measurement configuration file. ureg: pint UnitRegistry, manages units. """ self.tdc_params = tdc_params self.constants = tdc_params['constants'] self.channels = tdc_params['channels'] self.ureg = ureg self.Q_ = ureg.Quantity self.fig, self.ax = plt.subplots(1,len(self.channels), figsize=(3*len(self.channels),3)) self.fig.patch.set_alpha(1) self.init_empty()
[docs] def init_empty(self): """Initialize the plot with no data. """ dV = self.Q_(self.tdc_params['dV']).to('V').magnitude startV, endV = sorted([self.Q_(lim).to('V').magnitude for lim in self.tdc_params['range']]) npnts = int((endV - startV) / dV) self.heights = np.linspace(startV, endV, npnts) for i, ch in enumerate(self.channels): self.ax[i].set_xlim(min(self.heights), max(self.heights)) self.ax[i].grid() self.ax[i].set_xlabel('z position [V]') self.ax[i].set_ylabel(r'{} [{}]'.format(self.channels[ch]['label'], self.channels[ch]['unit_latex'])) self.ax[i].set_title(self.channels[ch]['label']) self.fig.canvas.draw() self.fig.tight_layout()
[docs] def update(self, data_set: Any) -> None: """Update plot with data from data_set. Args: data_set: DataSet generated by Loop in Microscope.td_cap(). """ self.location = data_set.location self.fig.suptitle(self.location, x=0.5, y=1, fontsize=10) self.prefactors = data_set.metadata['loop']['metadata']['prefactors'] all_data = to_real_units(data_set) npnts = len(all_data[:,0,0][np.isfinite(all_data[:,0,0])]) self.hdata = self.heights[:npnts] for i, ch in enumerate(self.channels): data = all_data[:,i,0][np.isfinite(all_data[:,i,0])] if len(self.hdata) == len(data): clear_artists(self.ax[i]) self.ax[i].plot(self.hdata, data, 'b.') self.ax[i].plot(self.hdata[-1], data[-1], 'r.') if ch == 'CAP': self.cdata = data elif ch == 'SUSCX': self.sxdata = data elif ch == 'SUSCY': self.sydata = data self.fig.canvas.draw()
[docs] def save(self, fname=None): """Save plot to png file. Args: fname: File to which to save the plot. If fname is None, saves to data location as {tdc_params['fname']}.png """ if fname is None: fname = os.path.join(self.location, self.tdc_params['fname'] + '.png') plt.savefig(fname, dpi=300)