From 6b600084fcbd01ef311b2d2efa712fe28b8e11e7 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 11 Sep 2017 18:14:27 +0200 Subject: [PATCH 01/11] Wip for scaling matplotlib plots with unit prefix --- qcodes/plots/base.py | 3 ++ qcodes/plots/qcmatplotlib.py | 63 +++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/base.py b/qcodes/plots/base.py index 618eb5175168..d42748bd54ac 100644 --- a/qcodes/plots/base.py +++ b/qcodes/plots/base.py @@ -24,6 +24,9 @@ def __init__(self, interval=1, data_keys='xyz'): self.traces = [] self.data_updaters = set() self.interval = interval + self.standardunits = ['V', 's', 'J', 'W', 'm', 'eV', 'A', 'K', 'g', + 'Hz', 'rad', 'T', 'H', 'F', 'Pa', 'C', 'Ω', 'Ohm', + 'S'] def clear(self): """ diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index c5ce80b7c767..fff3b36c62a4 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -7,6 +7,7 @@ from functools import partial import matplotlib.pyplot as plt +from matplotlib import ticker import numpy as np from matplotlib.transforms import Bbox from numpy.ma import masked_invalid, getmask @@ -399,4 +400,64 @@ def tight_layout(self): Perform a tight layout on the figure. A bit of additional spacing at the top is also added for the title. """ - self.fig.tight_layout(rect=[0, 0, 1, 0.95]) \ No newline at end of file + self.fig.tight_layout(rect=[0, 0, 1, 0.95]) + + def rescale_axis(self): + """ + Rescale axis and units for axis that are in standard units + i.e. V, s J ... to m μ, m + """ + def scale_formatter(i, pos, scale): + return "{0:g}".format(i * scale) + + for i, subplot in enumerate(self.subplots): + for axis in 'x', 'y', 'z': + if self.traces[i]['config'].get(axis): + unit = self.traces[i]['config'][axis].unit + label = self.traces[i]['config'][axis].label + maxval = abs(self.traces[i]['config'][axis].ndarray).max() + units_to_scale = self.standardunits + + # allow values up to a <1000. i.e. nV is used up to 1000 nV + smallerthan = [1e-6, 1e-3, 1, 1e3, 1e6, 1e9, 1e12] + prefixes= ['n', 'μ', 'm', '', 'k', 'M', 'G'] + + if unit in units_to_scale: + for trialscale, prefix in zip(smallerthan, prefixes): + if maxval < trialscale: + scale = trialscale + new_unit = prefix + unit + break + # special case the largest + if maxval > smallerthan[-1]: + scale = smallerthan[-1] + new_unit = prefixes[-1] + unit + + + + # if maxval < 1e-6: + # scale = 1e9 + # new_unit = "n" + unit + # elif maxval < 1e-3: + # scale = 1e6 + # new_unit = "μ" + unit + # elif maxval < 1: + # scale = 1e3 + # new_unit = "m" + unit + # else: + # continue + tx = ticker.FuncFormatter( + partial(scale_formatter, scale=scale)) + new_label = "{} ({})".format(label, new_unit) + if axis in ('x', 'y'): + getattr(subplot, + "{}axis".format(axis)).set_major_formatter( + tx) + getattr(subplot, "set_{}label".format(axis))( + new_label) + else: + subplot.qcodes_colorbar.formatter = tx + subplot.qcodes_colorbar.ax.yaxis.set_major_formatter( + tx) + subplot.qcodes_colorbar.set_label(new_label) + subplot.qcodes_colorbar.update_ticks() From 06c989e6be3c0183338d661ac4c96737262eea28 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 12 Sep 2017 10:55:26 +0200 Subject: [PATCH 02/11] fix and make scaling more general --- qcodes/plots/qcmatplotlib.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index fff3b36c62a4..dc7e811728a5 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -419,33 +419,25 @@ def scale_formatter(i, pos, scale): units_to_scale = self.standardunits # allow values up to a <1000. i.e. nV is used up to 1000 nV - smallerthan = [1e-6, 1e-3, 1, 1e3, 1e6, 1e9, 1e12] - prefixes= ['n', 'μ', 'm', '', 'k', 'M', 'G'] + prefixes = ['n', 'μ', 'm', '', 'k', 'M', 'G'] + thresholds = [10**(-6 + 3*n) for n in range(len(prefixes))] + scales = [10 ** (9 - 3*n) for n in range(len(prefixes))] if unit in units_to_scale: - for trialscale, prefix in zip(smallerthan, prefixes): - if maxval < trialscale: + scale = 1 + new_unit = unit + for prefix, threshold, trialscale in zip(prefixes, + thresholds, + scales): + if maxval < threshold: scale = trialscale new_unit = prefix + unit break # special case the largest - if maxval > smallerthan[-1]: - scale = smallerthan[-1] + if maxval > thresholds[-1]: + scale = scales[-1] new_unit = prefixes[-1] + unit - - - # if maxval < 1e-6: - # scale = 1e9 - # new_unit = "n" + unit - # elif maxval < 1e-3: - # scale = 1e6 - # new_unit = "μ" + unit - # elif maxval < 1: - # scale = 1e3 - # new_unit = "m" + unit - # else: - # continue tx = ticker.FuncFormatter( partial(scale_formatter, scale=scale)) new_label = "{} ({})".format(label, new_unit) From 753130ced7e4fd29071862c6600daac77a7f8778 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 12 Sep 2017 10:56:57 +0200 Subject: [PATCH 03/11] docs --- qcodes/plots/qcmatplotlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index dc7e811728a5..6bc0df128504 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -406,6 +406,8 @@ def rescale_axis(self): """ Rescale axis and units for axis that are in standard units i.e. V, s J ... to m μ, m + This scales units defined in BasePlot.standardunits only + to avoid prefixes on combined or non standard units """ def scale_formatter(i, pos, scale): return "{0:g}".format(i * scale) @@ -421,7 +423,7 @@ def scale_formatter(i, pos, scale): # allow values up to a <1000. i.e. nV is used up to 1000 nV prefixes = ['n', 'μ', 'm', '', 'k', 'M', 'G'] thresholds = [10**(-6 + 3*n) for n in range(len(prefixes))] - scales = [10 ** (9 - 3*n) for n in range(len(prefixes))] + scales = [10**(9 - 3*n) for n in range(len(prefixes))] if unit in units_to_scale: scale = 1 From e962a8b26008d895f1f85519ca68eb5e67af5ed1 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 12 Sep 2017 15:23:37 +0200 Subject: [PATCH 04/11] Add pyqtgraph method to fix autoscaling of non std units --- qcodes/plots/pyqtgraph.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 97b585178771..bc3a873dd384 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -475,3 +475,35 @@ def save(self, filename=None): def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) + + def fixUnitScaling(self): + """ + Disable SI rescaling if units are not standard units + """ + axismapping = {'x': 'bottom', + 'y': 'left', + 'z': 'right'} + standardunits = self.standardunits + for i, plot in enumerate(self.subplots): + # make a dict mapping axis labels to axis positions + for axis in ('x', 'y', 'z'): + if self.traces[i]['config'].get(axis): + unit = self.traces[i]['config'][axis].unit + label = self.traces[i]['config'][axis].label + #maxval = abs(self.traces[i]['config'][axis].ndarray).max() + if unit not in standardunits: + if axis in ('x', 'y'): + ax = plot.getAxis(axismapping[axis]) + else: + # 2D measurement + # Then we should fetch the colorbar + ax = self.traces[i]['plot_object']['hist'].axis + ax.enableAutoSIPrefix(False) + # because updateAutoSIPrefix called from + # enableAutoSIPrefix doesnt actually take the + # value of the argument into account we have + # to manually replicate the update here + ax.autoSIPrefixScale = 1.0 + ax.setLabel(unitPrefix='') + ax.picture = None + ax.update() From ddded63443acb22590b9f2062525d56e49c64ec8 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 12 Sep 2017 15:31:55 +0200 Subject: [PATCH 05/11] remove unused lines --- qcodes/plots/pyqtgraph.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index bc3a873dd384..68c6efae7d4f 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -489,8 +489,6 @@ def fixUnitScaling(self): for axis in ('x', 'y', 'z'): if self.traces[i]['config'].get(axis): unit = self.traces[i]['config'][axis].unit - label = self.traces[i]['config'][axis].label - #maxval = abs(self.traces[i]['config'][axis].ndarray).max() if unit not in standardunits: if axis in ('x', 'y'): ax = plot.getAxis(axismapping[axis]) From 4e9d1928f34a113e5f7a95cfc71643582cefe468 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 11:41:20 +0200 Subject: [PATCH 06/11] Add rescaling to scaling function to match wrappers --- qcodes/plots/pyqtgraph.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 68c6efae7d4f..06c09b24650f 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -476,7 +476,7 @@ def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) - def fixUnitScaling(self): + def fixUnitScaling(self, startranges=None): """ Disable SI rescaling if units are not standard units """ @@ -505,3 +505,29 @@ def fixUnitScaling(self): ax.setLabel(unitPrefix='') ax.picture = None ax.update() + + # set limits either from dataset or + setarr = self.traces[i]['config'][axis].ndarray + arrmin = None + arrmax = None + if not np.all(np.isnan(setarr)): + arrmax = setarr.max() + arrmin = setarr.min() + elif startranges is not None: + try: + paramname = self.traces[i]['config'][axis].full_name + arrmax = startranges[paramname]['max'] + arrmin = startranges[paramname]['min'] + except (IndexError, KeyError): + continue + + if axis == 'x': + rangesetter = getattr(plot.getViewBox(), 'setXRange') + elif axis == 'y': + rangesetter = getattr(plot.getViewBox(), 'setYRange') + else: + rangesetter = None + + if rangesetter is not None and arrmin is not None and arrmax is not None: + rangesetter(arrmin, arrmax) + From dfc03d6c95733a3e628b6f20aa7514703822475f Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 11:48:09 +0200 Subject: [PATCH 07/11] Add typing --- qcodes/plots/pyqtgraph.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 06c09b24650f..70d49d6ddb53 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -1,6 +1,7 @@ """ Live plotting using pyqtgraph """ +from typing import Optional, Dict, Union import numpy as np import pyqtgraph as pg import pyqtgraph.multiprocess as pgmp @@ -476,7 +477,7 @@ def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) - def fixUnitScaling(self, startranges=None): + def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,int]]]]=None): """ Disable SI rescaling if units are not standard units """ @@ -528,6 +529,8 @@ def fixUnitScaling(self, startranges=None): else: rangesetter = None - if rangesetter is not None and arrmin is not None and arrmax is not None: + if (rangesetter is not None + and arrmin is not None + and arrmax is not None): rangesetter(arrmin, arrmax) From 3197bc246e057eb63379494b8d837d961bc6303b Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 16:02:12 +0200 Subject: [PATCH 08/11] call autoscale when a plot is created --- qcodes/plots/pyqtgraph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 70d49d6ddb53..c9657d432c73 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -145,6 +145,7 @@ def add_to_plot(self, subplot=1, **kwargs): if prev_default_title == self.win.windowTitle(): self.win.setWindowTitle(self.get_default_title()) + self.fixUnitScaling() def _draw_plot(self, subplot_object, y, x=None, color=None, width=None, antialias=None, **kwargs): From d9473061be490d0d2cc7e2826e08cbe530c1e4f7 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 16:02:28 +0200 Subject: [PATCH 09/11] Add docstring --- qcodes/plots/pyqtgraph.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index c9657d432c73..9e6c7c36866c 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -480,7 +480,15 @@ def setGeometry(self, x, y, w, h): def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,int]]]]=None): """ - Disable SI rescaling if units are not standard units + Disable SI rescaling if units are not standard units. + + Args: + + startranges: The plot can automatically infer the full ranges + array parameters. However it has no knowledge of the + ranges or regular parameters. You can explicitly pass + in the values here as a dict of the form + {'paramtername': {max: value, min:value}} """ axismapping = {'x': 'bottom', 'y': 'left', From a9b27623503fc080051dc065f721f15c9c0ecaba Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 13 Sep 2017 16:31:49 +0200 Subject: [PATCH 10/11] Add function to reset limits and scales --- qcodes/plots/pyqtgraph.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 9e6c7c36866c..22254248bcdd 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -81,6 +81,7 @@ def __init__(self, *args, figsize=(1000, 600), interval=0.25, raise err self.win.setBackground(theme[1]) self.win.resize(*figsize) + self._orig_fig_size = figsize self.subplots = [self.add_subplot()] if args or kwargs: @@ -478,9 +479,34 @@ def setGeometry(self, x, y, w, h): """ Set geometry of the plotting window """ self.win.setGeometry(x, y, w, h) + def autorange(self): + """ + Auto range all limits in case they were changed during interactive + plot. Reset colormap if changed and resize window to original size. + """ + for subplot in self.subplots: + vBox = subplot.getViewBox() + vBox.enableAutoRange(vBox.XYAxes) + cmap = None + # resize histogram + for trace in self.traces: + if 'plot_object' in trace.keys(): + if (isinstance(trace['plot_object'], dict) and + 'hist' in trace['plot_object'].keys()): + cmap = trace['plot_object']['cmap'] + 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: + self.set_cmap(cmap) + # set window back to original size + self.win.resize(*self._orig_fig_size) + def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,int]]]]=None): """ - Disable SI rescaling if units are not standard units. + Disable SI rescaling if units are not standard units and limit + ranges to data if known. Args: From 94aa88f421296dfbfddc41f369e24b3c0e26812f Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 18 Sep 2017 10:16:15 +0200 Subject: [PATCH 11/11] fix: dont shadow buildin --- qcodes/plots/pyqtgraph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 22254248bcdd..5864803bf31f 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -494,9 +494,9 @@ def autorange(self): 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) + maxval = trace['config']['z'].max() + minval = trace['config']['z'].min() + trace['plot_object']['hist'].setLevels(minval, maxval) trace['plot_object']['hist'].vb.autoRange() if cmap: self.set_cmap(cmap)