Skip to content
Snippets Groups Projects
Commit 10f9b845 authored by Tammo Jan Dijkema's avatar Tammo Jan Dijkema
Browse files

Task #5049: make parmdbplot more user friendly, add some features

parent 51b9898d
No related branches found
No related tags found
No related merge requests found
...@@ -20,13 +20,7 @@ ...@@ -20,13 +20,7 @@
# #
# $Id$ # $Id$
import sys import sys, copy, math, numpy, signal
import lofar.parmdb as parmdb
import copy
import math
import numpy
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.figure import Figure from matplotlib.figure import Figure
...@@ -35,6 +29,9 @@ from matplotlib.font_manager import FontProperties ...@@ -35,6 +29,9 @@ from matplotlib.font_manager import FontProperties
from PyQt4.QtCore import * from PyQt4.QtCore import *
from PyQt4.QtGui import * from PyQt4.QtGui import *
import lofar.parmdb as parmdb
signal.signal(signal.SIGINT, signal.SIG_DFL)
__styles = ["%s%s" % (x, y) for y in ["-", ":"] for x in ["b", "g", "r", "c", __styles = ["%s%s" % (x, y) for y in ["-", ":"] for x in ["b", "g", "r", "c",
"m", "y", "k"]] "m", "y", "k"]]
...@@ -51,7 +48,7 @@ def common_domain(parms): ...@@ -51,7 +48,7 @@ def common_domain(parms):
domain = [-1e30, 1e30, -1e30, 1e30] domain = [-1e30, 1e30, -1e30, 1e30]
for parm in parms: for parm in parms:
tmp = parm.domain() tmp = parm.domain()
domain = [max(domain[0], tmp[0]), min(domain[1], tmp[1]), max(domain[2], tmp[2]), min(domain, tmp[3])] domain = [max(domain[0], tmp[0]), min(domain[1], tmp[1]), max(domain[2], tmp[2]), min(domain[3], tmp[3])]
if domain[0] >= domain[1] or domain[2] >= domain[3]: if domain[0] >= domain[1] or domain[2] >= domain[3]:
return None return None
...@@ -178,7 +175,7 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False, ...@@ -178,7 +175,7 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False,
The 'labels' argument can be set to a list of labels and 'show_legend' can The 'labels' argument can be set to a list of labels and 'show_legend' can
be set to True to show a legend inside the plot. be set to True to show a legend inside the plot.
The figure number of the figure used to plot in is returned. The min/max/median of the plotted points are returned is returned.
""" """
global __styles global __styles
...@@ -199,7 +196,8 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False, ...@@ -199,7 +196,8 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False,
if x is None: if x is None:
x = [range(len(yi)) for yi in y] x = [range(len(yi)) for yi in y]
offset = 0.0 offset = 0.
med = 0.
for i in range(0,len(y)): for i in range(0,len(y)):
if labels is None: if labels is None:
if scatter: if scatter:
...@@ -216,7 +214,8 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False, ...@@ -216,7 +214,8 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False,
axes.plot(x[i], y[i] + offset, __styles[i % len(__styles)], axes.plot(x[i], y[i] + offset, __styles[i % len(__styles)],
label=labels[i]) label=labels[i])
if stack: med += numpy.median(y[i] + offset)
if stack and i != len(y)-1:
if sep_abs: if sep_abs:
offset += sep offset += sep
else: else:
...@@ -225,73 +224,120 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False, ...@@ -225,73 +224,120 @@ def plot(fig, y, x=None, clf=True, sub=None, scatter=False, stack=False,
if not labels is None and show_legend: if not labels is None and show_legend:
axes.legend(prop=FontProperties(size="x-small"), markerscale=0.5) axes.legend(prop=FontProperties(size="x-small"), markerscale=0.5)
# return min max median values
if stack:
return numpy.min(y[0]), numpy.max(y[-1]+offset), med/len(y)
else:
return numpy.min(y), numpy.max(y), med/len(y)
class Parm: class Parm:
def __init__(self, db, name, elements=None, isPolar=False): """
Each entry in the parmdb is an instance of this class
"""
def __init__(self, db, name, elements=None, isPolar=True):
self._db = db self._db = db
self._name = name self._name = name
self._elements = elements self._elements = elements
self._isPolar = isPolar self._isPolar = isPolar
self._value = None self._value = None
self._freq = None
self._value_domain = None self._value_domain = None
self._value_resolution = None self._value_resolution = None
self._calType = self._name.split(':')[0]
if self._calType == 'DirectionalGain' or self._calType == 'RotationAngle': self._antenna = self._name.split(':')[-2]
else: self._antenna = self._name.split(':')[-1]
# modifiers for some phase outputs
if self._calType == 'Clock':
self.mod = lambda Clock: 2. * numpy.pi * Clock * self._freq
elif self._calType == 'TEC':
self.mod = lambda TEC: -8.44797245e9 * TEC / self._freq
elif self._calType == 'RM':
self.mod = lambda RM: RM / (299792458.*self._freq*299792458.*self._freq)
else:
# everything else is plain
self.mod = lambda ph: ph
self._readDomain() self._readDomain()
def name(self):
return self._name
def isPolar(self):
return self._isPolar
def empty(self): def empty(self):
return self._empty return self._empty
def domain(self): def domain(self):
return self._domain return self._domain
def value(self, domain=None, resolution=None, asPolar=True, unwrap_phase=False, phase_reference=None): def color(self):
if self.empty(): if self._calType == 'Gain': return QColor('#ffbfca')
return (numpy.zeros((1,1)), numpy.zeros((1,1))) if self._calType == 'DirectionalGain': return QColor('#ebbfff')
if self._calType == 'CommonRotationAngle': return QColor('#bfcdff')
if self._value is None or self._value_domain != domain or self._value_resolution != resolution: if self._calType == 'RotationAngle': return QColor('#bfe5ff')
self._readValue(domain, resolution) if self._calType == 'Clock': return QColor('#fff1bf')
if self._calType == 'TEC': return QColor('#bfffda')
if self._calType == 'CommonScalarPhase': return QColor('#ffbfbf')
if self._calType == 'RM': return QColor('#84f0aa')
# Correct negative amplitude solutions by taking the absolute value def valueAmp(self, domain=None, resolution=None, asPolar=True):
# of the amplitude and rotating the phase by 180 deg. self.updateValue(domain, resolution)
if self.isPolar():
self._value[1][self._value[0] < 0.0] += numpy.pi
self._value[0] = numpy.abs(self._value[0])
if asPolar: if asPolar:
if self.isPolar(): if self._isPolar:
ampl = self._value[0] ampl = self._value[0]
phase = normalize(self._value[1])
else: else:
ampl = numpy.sqrt(numpy.power(self._value[0], 2) + numpy.power(self._value[1], 2)) ampl = numpy.sqrt(numpy.power(self._value[0], 2) + numpy.power(self._value[1], 2))
return ampl
if not self._isPolar:
re = self._value[0]
else:
re = self._value[0] * numpy.cos(self._value[1])
return re
def valuePhase(self, domain=None, resolution=None, asPolar=True, unwrap_phase=False, \
reference_parm=None, sum_parms=[], mod=True):
self.updateValue(domain, resolution)
if asPolar:
if self._isPolar:
phase = self._value[1]
else:
phase = numpy.arctan2(self._value[1], self._value[0]) phase = numpy.arctan2(self._value[1], self._value[0])
if not phase_reference is None: # apply modifiers for some solution types (e.g. clock, TEC, RM)
assert(phase_reference.shape == phase.shape) phase = self.mod(phase)
phase = normalize(phase - phase_reference)
if not reference_parm is None:
reference_val = reference_parm.valuePhase(domain, resolution)
assert(reference_val.shape == phase.shape)
phase = normalize(phase - reference_val)
for sum_parm in sum_parms:
sum_val = sum_parm[0].valuePhase(domain, resolution)
sum_reference_val = sum_parm[1].valuePhase(domain, resolution)
assert(sum_val.shape == phase.shape)
assert(sum_reference_val.shape == phase.shape)
phase = normalize(phase + sum_val - sum_reference_val)
if mod: phase = normalize(phase)
if unwrap_phase: if unwrap_phase:
for i in range(0, phase.shape[1]): for i in range(0, phase.shape[1]):
phase[:, i] = unwrap(phase[:, i]) phase[:, i] = unwrap(phase[:, i])
return [ampl, phase] return phase
if not self.isPolar(): if not self._isPolar:
re = self._value[0]
im = self._value[1] im = self._value[1]
else: else:
re = self._value[0] * numpy.cos(self._value[1])
im = self._value[0] * numpy.sin(self._value[1]) im = self._value[0] * numpy.sin(self._value[1])
return [re, im] return im
def _readDomain(self): def _readDomain(self):
if self._elements is None: if self._elements is None:
self._domain = self._db.getRange(self.name()) self._domain = self._db.getRange(self._name)
else: else:
if self._elements[0] is None: if self._elements[0] is None:
self._domain = self._db.getRange(self._elements[1]) self._domain = self._db.getRange(self._elements[1])
...@@ -304,10 +350,24 @@ class Parm: ...@@ -304,10 +350,24 @@ class Parm:
self._empty = (self._domain[0] >= self._domain[1]) or (self._domain[2] >= self._domain[3]) self._empty = (self._domain[0] >= self._domain[1]) or (self._domain[2] >= self._domain[3])
def updateValue(self, domain=None, resolution=None):
if self.empty():
return (numpy.zeros((1,1)), numpy.zeros((1,1)))
# if nothig changes, use cache
if self._value is None or self._value_domain != domain or self._value_resolution != resolution:
self._readValue(domain, resolution)
# Correct negative amplitude solutions by taking the absolute value
# of the amplitude and rotating the phase by 180 deg.
if self._isPolar and (self._calType == 'Gain' or self._calType == 'DirectionalGain'):
self._value[1][self._value[0] < 0.0] += numpy.pi
self._value[0] = numpy.abs(self._value[0])
def _readValue(self, domain=None, resolution=None): def _readValue(self, domain=None, resolution=None):
if self._elements is None: if self._elements is None:
value = numpy.array(self.__fetch_value(self.name(), domain, resolution)) value = numpy.array(self.__fetch_value(self._name, domain, resolution))
self._value = (value, numpy.zeros(value.shape)) self._value = (numpy.zeros(value.shape), value)
else: else:
el0 = None el0 = None
if not self._elements[0] is None: if not self._elements[0] is None:
...@@ -335,15 +395,17 @@ class Parm: ...@@ -335,15 +395,17 @@ class Parm:
tmp = self._db.getValuesGrid(name)[name] tmp = self._db.getValuesGrid(name)[name]
else: else:
if resolution is None: if resolution is None:
tmp = self._db.getValues(name, domain[0], domain[1], domain[2], domain[3])[name] tmp = self._db.getValuesGrid(name, domain[0], domain[1], domain[2], domain[3])[name]
else: else:
tmp = self._db.getValuesStep(name, domain[0], domain[1], resolution[0], domain[2], domain[3], resolution[1])[name] tmp = self._db.getValuesStep(name, domain[0], domain[1], resolution[0], domain[2], domain[3], resolution[1])[name]
if type(tmp) is dict: # store frequencies
return tmp["values"] self._freq = numpy.array([tmp["freqs"]] * len(tmp["values"]))
# Old parmdb interface. # store times and frequencies
return tmp self._freqs = numpy.array(tmp["freqs"])
self._times = numpy.array(tmp["times"])
return tmp["values"]
class PlotWindow(QFrame): class PlotWindow(QFrame):
def __init__(self, parms, selection, resolution=None, parent=None, title=None): def __init__(self, parms, selection, resolution=None, parent=None, title=None):
...@@ -355,15 +417,23 @@ class PlotWindow(QFrame): ...@@ -355,15 +417,23 @@ class PlotWindow(QFrame):
self.parms = parms self.parms = parms
self.selected_parms = [self.parms[i] for i in selection] self.selected_parms = [self.parms[i] for i in selection]
self.calType = self.selected_parms[0]._calType
self.fig = Figure((5, 4), dpi=100) self.fig = Figure((5, 4), dpi=100)
self.canvas = FigureCanvas(self.fig) self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self) self.canvas.setParent(self)
self.toolbar = NavigationToolbar(self.canvas, self)
self.axis = 0 self.axis = 0
self.index = 0 self.index = 0
self.zoom = 0
self.valminmax = [[],[]]
self.blocked_valminmax = [[],[]]
self.xminmax =[]
self.toolbar = NavigationToolbar(self.canvas, self)
self.axisSelector = QComboBox() self.axisSelector = QComboBox()
self.axisSelector.addItem("Frequency") self.axisSelector.addItem("Frequency")
self.axisSelector.addItem("Time") self.axisSelector.addItem("Time")
...@@ -372,15 +442,33 @@ class PlotWindow(QFrame): ...@@ -372,15 +442,33 @@ class PlotWindow(QFrame):
self.spinner = QSpinBox() self.spinner = QSpinBox()
self.connect(self.spinner, SIGNAL('valueChanged(int)'), self.handle_spinner) self.connect(self.spinner, SIGNAL('valueChanged(int)'), self.handle_spinner)
# self.slider = QSlider(Qt.Horizontal) self.slider = QSlider(Qt.Vertical)
# self.slider.setMinimum(0) self.slider.setMinimum(0)
# self.slider.setMaximum(159) self.slider.setMaximum(100)
# self.connect(self.slider, SIGNAL('sliderReleased()'), self.handle_slider) self.slider.setTracking(False)
self.slider.setToolTip('Zoom in on the median of amplitude')
self.connect(self.slider, SIGNAL('valueChanged(int)'), self.handle_slider)
self.show_legend = False self.show_legend = False
self.legendCheck = QCheckBox("Legend") self.legendCheck = QCheckBox("Legend")
self.connect(self.legendCheck, SIGNAL('stateChanged(int)'), self.handle_legend) self.connect(self.legendCheck, SIGNAL('stateChanged(int)'), self.handle_legend)
self.block_axis = False
self.blockaxisCheck = QCheckBox("Block y-axis")
self.blockaxisCheck.setToolTip('Block y-axis when stepping through time or frequency')
self.connect(self.blockaxisCheck, SIGNAL('stateChanged(int)'), self.handle_blockaxis)
self.use_points = False
self.usepointsCheck = QCheckBox("Use points")
self.usepointsCheck.setToolTip('Use points for plotting amplitude')
self.connect(self.usepointsCheck, SIGNAL('stateChanged(int)'), self.handle_usepoints)
self.valuesonxaxis = False
self.valuesonxaxisCheck = QCheckBox("Values on x-axis")
self.valuesonxaxisCheck.setChecked(False)
self.valuesonxaxisCheck.setToolTip('Plot the value of time / frequency on x-axis (if unchecked, the sample number is shown)')
self.connect(self.valuesonxaxisCheck, SIGNAL('stateChanged(int)'), self.handle_valuesonxaxis)
self.polar = True self.polar = True
self.polarCheck = QCheckBox("Polar") self.polarCheck = QCheckBox("Polar")
self.polarCheck.setChecked(True) self.polarCheck.setChecked(True)
...@@ -390,34 +478,34 @@ class PlotWindow(QFrame): ...@@ -390,34 +478,34 @@ class PlotWindow(QFrame):
self.unwrapCheck = QCheckBox("Unwrap phase") self.unwrapCheck = QCheckBox("Unwrap phase")
self.connect(self.unwrapCheck, SIGNAL('stateChanged(int)'), self.handle_unwrap) self.connect(self.unwrapCheck, SIGNAL('stateChanged(int)'), self.handle_unwrap)
self.reference = None self.sum_parm = []
self.sum_index = []
self.sumSelector = QComboBox()
self.sumSelector.setToolTip('Show the sum of multiple phase effects (\'+\': include in sum)')
self.sumSelector.addItem('+'+self.selected_parms[0]._name)
# select only parm with the same antenna, exclude *RotationAngle: that's no phase
self.idxParmsSameAnt = [i for i, parm in enumerate(self.parms) if parm._antenna in \
[parm2._antenna for parm2 in self.selected_parms] \
and parm._calType!="CommonRotationAngle" and parm._calType!="RotationAngle"
and parm._name!=self.selected_parms[0]._name]
for idx in self.idxParmsSameAnt:
self.sumSelector.addItem(parms[idx]._name)
self.connect(self.sumSelector, SIGNAL('activated(int)'), self.handle_sum)
self.sumSelector.setEnabled(False)
self.sumLabel = QLabel("Phase sum:")
self.sumLabel.setEnabled(False)
self.reference_parm = None
self.reference_index = 0 self.reference_index = 0
self.referenceSelector = QComboBox() self.referenceSelector = QComboBox()
self.referenceSelector.addItem("None") self.referenceSelector.addItem("None")
for parm in self.parms: self.idxParmsSameType = [i for i, parm in enumerate(self.parms) if parm._calType == self.calType]
if parm.isPolar(): for idx in self.idxParmsSameType:
self.referenceSelector.addItem("%s (polar)" % parm.name()) self.referenceSelector.addItem(parms[idx]._name)
else:
self.referenceSelector.addItem(parm.name())
self.connect(self.referenceSelector, SIGNAL('activated(int)'), self.handle_reference) self.connect(self.referenceSelector, SIGNAL('activated(int)'), self.handle_reference)
self.referenceLabel = QLabel("Phase reference") self.referenceLabel = QLabel("Phase reference:")
hbox = QHBoxLayout()
hbox.addWidget(self.axisSelector)
hbox.addWidget(self.spinner)
hbox.addWidget(self.legendCheck)
hbox.addWidget(self.polarCheck)
hbox.addWidget(self.unwrapCheck)
hbox.addWidget(self.referenceSelector)
hbox.addWidget(self.referenceLabel)
hbox.addStretch(1)
layout = QVBoxLayout()
layout.addLayout(hbox);
layout.addWidget(self.canvas, 1)
layout.addWidget(self.toolbar)
self.setLayout(layout)
self.domain = common_domain(self.selected_parms) self.domain = common_domain(self.selected_parms)
...@@ -428,51 +516,63 @@ class PlotWindow(QFrame): ...@@ -428,51 +516,63 @@ class PlotWindow(QFrame):
self.shape = (1, 1) self.shape = (1, 1)
if not self.domain is None: if not self.domain is None:
self.shape = (self.selected_parms[0].value(self.domain, self.resolution)[0].shape) self.shape = (self.selected_parms[0].valueAmp(self.domain, self.resolution).shape)
assert(len(self.shape) == 2) assert(len(self.shape) == 2)
self.spinner.setRange(0, self.shape[1 - self.axis] - 1) self.spinner.setRange(0, self.shape[1 - self.axis] - 1)
self.plot()
def plot(self): # display widgets
el0 = [] hbox = QHBoxLayout()
el1 = [] hbox.addWidget(self.axisSelector)
labels = [] hbox.addWidget(self.spinner)
hbox.addWidget(self.legendCheck)
if not self.domain is None: # Re/Imag or Ampl/Phase only relevant for gains
for parm in self.selected_parms: if self.calType=="Gain" or self.calType=="DirectionalGain":
value = parm.value(self.domain, self.resolution, self.polar, self.unwrap_phase, self.reference) hbox.addWidget(self.polarCheck)
if value[0].shape != self.shape or value[1].shape != self.shape: hbox.addWidget(self.unwrapCheck)
print "warning: inconsistent shape; will skip parameter:", parm.name() hbox.addWidget(self.blockaxisCheck)
continue
if self.axis == 0:
el0.append(value[0][:, self.index])
el1.append(value[1][:, self.index])
else:
el0.append(value[0][self.index, :])
el1.append(value[1][self.index, :])
labels.append(parm.name())
legend = self.show_legend and len(labels) > 0 # Phases are always points, amplitudes (gains) are lines by default
xlabel = ["Time (sample)", "Freq (sample)"][self.axis] if self.calType=="Gain" or self.calType=="DirectionalGain":
if self.polar: hbox.addWidget(self.usepointsCheck)
plot(self.fig, el0, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Amplitude") hbox.addWidget(self.valuesonxaxisCheck)
plot(self.fig, el1, clf=False, sub="212", stack=True, scatter=True, labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Phase (rad)") hbox.addStretch(1)
else: hbox2 = QHBoxLayout()
plot(self.fig, el0, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Real") hbox2.addWidget(self.referenceLabel)
plot(self.fig, el1, clf=False, sub="212", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Imaginary") hbox2.addWidget(self.referenceSelector)
# For now, phase sum only works when only one parameter is shown
if len(self.selected_parms)==1:
hbox2.addWidget(self.sumLabel)
hbox2.addWidget(self.sumSelector)
hbox2.addStretch(1)
hbox3 = QHBoxLayout()
# Zooming in only possible on amplitudes (gains)
if self.calType=="Gain" or self.calType=="DirectionalGain":
hbox3.addWidget(self.slider)
hbox3.addWidget(self.canvas)
layout = QVBoxLayout()
layout.addLayout(hbox)
# Set x-axis scale in number of samples. # RotationAngles do not have phase sum
for ax in self.fig.axes: if self.calType!="CommonRotationAngle" and self.calType!="RotationAngle":
ax.set_xlim(0, self.shape[self.axis] - 1) layout.addLayout(hbox2)
layout.addLayout(hbox3)
layout.addWidget(self.toolbar)
self.setLayout(layout)
self.canvas.draw() self.plot()
def handle_spinner(self, index): def handle_spinner(self, index):
self.index = index self.index = index
self.plot() self.plot()
def handle_slider(self, zoom):
self.zoom = zoom
self.resize_plot()
self.canvas.draw()
def handle_axis(self, axis): def handle_axis(self, axis):
if axis != self.axis: if axis != self.axis:
self.axis = axis self.axis = axis
...@@ -480,6 +580,47 @@ class PlotWindow(QFrame): ...@@ -480,6 +580,47 @@ class PlotWindow(QFrame):
self.spinner.setValue(0) self.spinner.setValue(0)
self.plot() self.plot()
def find_reference(self, parm, ant_parm):
"""
find the parm which is a suitable reference for "parm".
The reference antenna is taken from ant_parm
"""
reference_antenna = ant_parm._antenna
reference_name = parm._name.replace(parm._antenna, reference_antenna)
reference_parm = next((p for p in self.parms if p._name == reference_name), None)
if reference_parm == None:
print "ERROR: cannot find a suitable reference (", reference_name ,") for", parm._name
return None
else:
# print "DEBUG: using reference ", reference_name ," for", parm._name
return reference_parm
def handle_sum(self, index):
if index == 0:
a=3 #do nothing
# self.sum_parm = []
# remove all the "+" from the drop down menu
# for sum_index in self.sum_index:
# parm = self.parms[self.idxParmsSameAnt[sum_index-1]]
# self.sumSelector.setItemText(sum_index, parm._name)
# self.sum_index = []
elif index in self.sum_index:
parm = self.parms[self.idxParmsSameAnt[index-1]]
self.sum_parm.pop(self.sum_index.index(index))
self.sum_index.remove(index)
# remove "+" from the selected element
self.sumSelector.setItemText(index, parm._name)
else:
parm = self.parms[self.idxParmsSameAnt[index-1]]
ant_parm = self.parms[self.idxParmsSameType[self.reference_index-1]]
sum_reference_parm = self.find_reference(parm, ant_parm)
# add a tuple of the parm to sum and its reference
self.sum_parm.append( (parm, sum_reference_parm) )
self.sum_index.append(index)
# add "+" to the selected element
self.sumSelector.setItemText(index, '+'+parm._name)
self.plot()
def handle_legend(self, state): def handle_legend(self, state):
self.show_legend = (state == 2) self.show_legend = (state == 2)
self.plot() self.plot()
...@@ -488,29 +629,153 @@ class PlotWindow(QFrame): ...@@ -488,29 +629,153 @@ class PlotWindow(QFrame):
self.unwrap_phase = (state == 2) self.unwrap_phase = (state == 2)
self.plot() self.plot()
def handle_blockaxis(self, state):
self.block_axis = (state == 2)
if self.block_axis:
self.slider.setEnabled(False)
for i, ax in enumerate(self.fig.axes):
self.blocked_valminmax[i] = (ax.axis()[2], ax.axis()[3], 0)
else:
self.slider.setEnabled(True)
self.plot()
def handle_usepoints(self, state):
self.use_points = (state == 2)
self.plot()
def handle_valuesonxaxis(self, state):
self.valuesonxaxis = (state == 2)
self.plot()
def handle_polar(self, state): def handle_polar(self, state):
self.polar = (state == 2) self.polar = (state == 2)
self.referenceSelector.setEnabled(self.polar) self.referenceSelector.setEnabled(self.polar)
self.referenceLabel.setEnabled(self.polar) self.referenceLabel.setEnabled(self.polar)
self.sumSelector.setEnabled(self.polar)
self.sumLabel.setEnabled(self.polar)
self.unwrapCheck.setEnabled(self.polar) self.unwrapCheck.setEnabled(self.polar)
self.plot() self.plot()
def handle_reference(self, index): def handle_reference(self, index):
if index != self.reference_index: if index != self.reference_index:
if index == 0: if index == 0:
reference = None self.reference_parm = None
self.sum_parm = []
# remove all the "+" from the drop down menu
for sum_index in self.sum_index:
parm = self.parms[self.idxParmsSameAnt[sum_index-1]]
self.sumSelector.setItemText(sum_index, parm._name)
self.sum_index = []
else: else:
parm = self.parms[index - 1] self.reference_parm = self.parms[self.idxParmsSameType[index-1]]
value = parm.value(self.domain, self.resolution)
if value[1].shape != self.shape: # update all the sums to use the new reference
print "warning: inconsistent shape; will not change phase reference to parameter:", parm.name() for i, sum_parm in enumerate(self.sum_parm):
return sum_reference_parm = self.find_reference(sum_parm[0], self.reference_parm)
reference = value[1] # add a tuple of the parm to sum and its reference
self.sum_parm[i] = (sum_parm[0], sum_reference_parm)
self.sumSelector.setEnabled((index != 0))
self.sumLabel.setEnabled((index != 0))
self.reference_index = index self.reference_index = index
self.reference = reference
self.plot() self.plot()
def resize_plot(self):
"""
Set y-axis scale looking the slider and the number of samples.
"""
for i, ax in enumerate(self.fig.axes):
# handle single x value (typically one freq)
if self.shape[self.axis] != 1:
#ax.set_xlim(0, self.shape[self.axis] - 1)
ax.set_xlim(self.xminmax[0],self.xminmax[1])
if self.block_axis == True:
ax.set_ylim(self.blocked_valminmax[i][0], self.blocked_valminmax[i][1])
else:
# handle single y value (typically y=0)
if self.valminmax[i][0] == self.valminmax[i][1]:
ax.set_ylim(self.valminmax[i][0]-1, self.valminmax[i][0]+1)
elif self.zoom == 0 or (self.polar and i>0):
ax.set_ylim(self.valminmax[i][0], self.valminmax[i][1])
else:
rng = (self.valminmax[i][1] - self.valminmax[i][0])/numpy.exp(self.zoom/10.)
ax.set_ylim(ymin = self.valminmax[i][2] - rng/2., \
ymax = self.valminmax[i][2] + rng/2.)
def plot(self):
"""
Prepare the plotting space, double makes a double plot (only [Directional]Gain)
"""
phase = []
amp = []
labels = []
xvalues = []
# Selector for double plot
if self.calType == 'Gain' or self.calType == 'DirectionalGain':
double = True
else:
self.usepointsCheck.setEnabled(False)
double = False
if not self.domain is None:
for parm in self.selected_parms:
valuePhase = parm.valuePhase(self.domain, self.resolution, asPolar = self.polar, \
unwrap_phase=self.unwrap_phase, reference_parm=self.reference_parm, sum_parms=self.sum_parm)
if self.axis == 0: # time on x-axis
phase.append(valuePhase[:, self.index])
else: # freq on x-axis
phase.append(valuePhase[self.index, :])
if self.valuesonxaxis:
if self.axis == 0: # time on x-axis
xvalues.append((parm._times-parm._times[0])/60.)
else: # freq on x-axis
xvalues.append(parm._freqs/1.e6)
else:
xvalues.append(range(len(phase[0])))
self.xminmax=[xvalues[0][0],xvalues[0][-1]]
if double:
valueAmp = parm.valueAmp(self.domain, self.resolution, asPolar = self.polar)
if self.axis == 0: # time on x-axis
amp.append(valueAmp[:, self.index])
else: # freq on x-axis
amp.append(valueAmp[self.index, :])
labels.append(parm._name)
legend = self.show_legend and len(labels) > 0
if self.valuesonxaxis:
xlabel = ["Time (minutes since start)", "Freq (MHz)"][self.axis]
else:
xlabel = ["Time (sample)", "Freq (sample)"][self.axis]
if self.calType == "CommonRotationAngle" or self.calType == "RotationAngle":
phaselabel = "Rotation angle (rad)"
else:
phaselabel = "Phase (rad)"
if double:
if self.polar:
self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Amplitude",scatter=self.use_points)
self.valminmax[1] = plot(self.fig, phase, x=xvalues, clf=False, sub="212", stack=True, scatter=True, labels=labels, show_legend=legend, xlabel=xlabel, ylabel=phaselabel)
else:
self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Real",scatter=self.use_points)
self.valminmax[1] = plot(self.fig, phase, x=xvalues, clf=False, sub="212", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Imaginary",scatter=self.use_points)
else:
self.valminmax[0] = plot(self.fig, phase, x=xvalues, sub="111", stack=True, scatter=True, labels=labels, show_legend=legend, xlabel=xlabel, ylabel=phaselabel)
self.resize_plot()
self.canvas.draw()
class MainWindow(QFrame): class MainWindow(QFrame):
def __init__(self, db): def __init__(self, db):
QFrame.__init__(self) QFrame.__init__(self)
...@@ -524,15 +789,12 @@ class MainWindow(QFrame): ...@@ -524,15 +789,12 @@ class MainWindow(QFrame):
self.list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list.setSelectionMode(QAbstractItemView.ExtendedSelection)
layout.addWidget(self.list, 1) layout.addWidget(self.list, 1)
self.useResolution = True self.useResolution = False
checkResolution = QCheckBox("Use resolution") checkResolution = QCheckBox("Use resolution")
checkResolution.setChecked(True) checkResolution.setChecked(False)
self.connect(checkResolution, SIGNAL('stateChanged(int)'), self.handle_resolution) self.connect(checkResolution, SIGNAL('stateChanged(int)'), self.handle_resolution)
self.resolution = [QLineEdit(), QLineEdit()] self.resolution = [QLineEdit(), QLineEdit()]
# validator = QDoubleValidator(self.resolution[0])
# validator.setRange(1.0, 2.0)
# self.resolution[0].setValidator(validator)
self.resolution[0].setAlignment(Qt.AlignRight) self.resolution[0].setAlignment(Qt.AlignRight)
self.resolution[1].setAlignment(Qt.AlignRight) self.resolution[1].setAlignment(Qt.AlignRight)
...@@ -563,6 +825,7 @@ class MainWindow(QFrame): ...@@ -563,6 +825,7 @@ class MainWindow(QFrame):
parm = names.pop() parm = names.pop()
split = parm.split(":") split = parm.split(":")
# just one entry for Real/Imag or Amp/Ph
if contains(split, "Real") or contains(split, "Imag"): if contains(split, "Real") or contains(split, "Imag"):
if contains(split, "Real"): if contains(split, "Real"):
idx = split.index("Real") idx = split.index("Real")
...@@ -584,7 +847,7 @@ class MainWindow(QFrame): ...@@ -584,7 +847,7 @@ class MainWindow(QFrame):
elements = [other, parm] elements = [other, parm]
split.pop(idx) split.pop(idx)
self.parms.append(Parm(self.db, ":".join(split), elements)) self.parms.append(Parm(self.db, ":".join(split), elements, isPolar = False))
elif contains(split, "Ampl") or contains(split, "Phase"): elif contains(split, "Ampl") or contains(split, "Phase"):
if contains(split, "Ampl"): if contains(split, "Ampl"):
...@@ -607,24 +870,25 @@ class MainWindow(QFrame): ...@@ -607,24 +870,25 @@ class MainWindow(QFrame):
elements = [other, parm] elements = [other, parm]
split.pop(idx) split.pop(idx)
self.parms.append(Parm(self.db, ":".join(split), elements, True)) self.parms.append(Parm(self.db, ":".join(split), elements, isPolar = True))
else: else:
self.parms.append(Parm(self.db, parm)) self.parms.append(Parm(self.db, parm))
self.parms = [parm for parm in self.parms if not parm.empty()] self.parms = [parm for parm in self.parms if not parm.empty()]
self.parms.sort(cmp=lambda x, y: cmp(x.name(), y.name())) self.parms.sort(cmp=lambda x, y: cmp(x._name, y._name))
domain = common_domain(self.parms) domain = common_domain(self.parms)
if not domain is None: if not domain is None:
self.resolution[0].setText("%.6f" % ((domain[1] - domain[0]) / 100.0)) self.resolution[0].setText("%.3f" % ((domain[1] - domain[0]) / 100.0))
self.resolution[1].setText("%.6f" % ((domain[3] - domain[2]) / 100.0)) self.resolution[1].setText("%.3f" % ((domain[3] - domain[2]) / 100.0))
for parm in self.parms: for parm in self.parms:
name = parm.name() name = parm._name
if parm.isPolar(): if parm._isPolar and (parm._calType == 'Gain' or parm._calType == 'DirectionalGain'):
name = "%s (polar)" % name name = "%s (polar)" % name
QListWidgetItem(name, self.list) it = QListWidgetItem(name, self.list)
it.setBackground(parm.color())
def close_all_figures(self): def close_all_figures(self):
for figure in self.figures: for figure in self.figures:
...@@ -643,7 +907,13 @@ class MainWindow(QFrame): ...@@ -643,7 +907,13 @@ class MainWindow(QFrame):
if self.useResolution: if self.useResolution:
resolution = [float(item.text()) for item in self.resolution] resolution = [float(item.text()) for item in self.resolution]
self.figures.append(PlotWindow(self.parms, selection, resolution, title="Figure %d" % (len(self.figures) + 1))) # plot together only parms of the same type
calTypes = set([self.parms[idx]._calType for idx in selection])
for calType in calTypes:
this_selection = [idx for idx in selection if self.parms[idx]._calType == calType]
self.figures.append(PlotWindow(self.parms, this_selection, resolution, title="Figure %d" % (len(self.figures) + 1)))
self.figures[-1].show() self.figures[-1].show()
def handle_close(self): def handle_close(self):
...@@ -659,7 +929,11 @@ if __name__ == "__main__": ...@@ -659,7 +929,11 @@ if __name__ == "__main__":
print "usage: parmdbplot.py <parmdb>" print "usage: parmdbplot.py <parmdb>"
sys.exit(1) sys.exit(1)
try:
db = parmdb.parmdb(sys.argv[1]) db = parmdb.parmdb(sys.argv[1])
except:
print "ERROR:", sys.argv[1], "is not a valid parmdb."
sys.exit(1)
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = MainWindow(db) window = MainWindow(db)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment