from PyQt5 import QtCore, QtWidgets
from ....core.gui.qt.widgets.edit import LVNumEdit
from ....core.utils.numerical import limit_to_range
import collections
[docs]class BinROICtl(QtWidgets.QWidget):
"""
Class for ROI control.
Has 2 rows (for X and Y coordinates), each with 3 numerical edits: min, max (or width, depending on :func:`setupUi` parameters), and bin.
Like most widgets, requires calling :meth:`setupUi` to set up before usage.
Args:
parent: parent widget
Attributes:
value_changed: signal emitted when the ROIvalue is changed
"""
AxisParams=collections.namedtuple("AxisParams",["min","max","bin"])
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self,parent)
self.xparams=self.AxisParams(0,1,1)
self.yparams=self.AxisParams(0,1,1)
self.validate=None
self.xlim=(0,None)
self.ylim=(0,None)
self.maxbin=None
self.minsize=0
def _limit_range(self, rng, lim, maxbin, minsize, maxsize):
vmin=limit_to_range(rng.min,*lim)
vmax=limit_to_range(rng.max,*lim)
vmin,vmax=min(vmin,vmax),max(vmin,vmax)
if vmax-vmin<minsize: # try increase upper limit
vmax=limit_to_range(vmin+minsize,*lim)
if vmax-vmin<minsize: # try decrease lower limit
vmin=limit_to_range(vmax-minsize,*lim)
if maxsize and (vmax-vmin>minsize):
vmax=vmin+maxsize
vbin=limit_to_range(rng.bin,1,maxbin)
return self.AxisParams(int(vmin),int(vmax),int(vbin))
[docs] def validateROI(self, xparams, yparams):
"""Restrict current ROI values according to the class constraints"""
xminsize,yminsize=self.minsize if isinstance(self.minsize,tuple) else (self.minsize,self.minsize)
xmaxsize,ymaxsize=self.maxsize if isinstance(self.maxsize,tuple) else (self.maxsize,self.maxsize)
xparams=self._limit_range(xparams,self.xlim,self.maxbin,xminsize,xmaxsize)
yparams=self._limit_range(yparams,self.ylim,self.maxbin,yminsize,ymaxsize)
if self.validate:
xparams,yparams=self.validate((xparams,yparams))
xparams=self.AxisParams(*xparams)
yparams=self.AxisParams(*yparams)
return xparams,yparams
[docs] def setupUi(self, name, xlim=(0,None), ylim=None, maxbin=None, minsize=0, maxsize=None, kind="minmax", validate=None):
"""
Setup the ROI control.
Args:
name (str): widget name
xlim (tuple): limit for x-axis min and max values
ylim (tuple): limit for y-axis min and max values
maxbin (int or tuple): maximal allowed binning (int imples same for both axes)
minsize (int or tuple): minimal allowed size (int imples same for both axes)
maxsize (int or tuple): maximal allowed size (int imples same for both axes)
kind (str): can be either ``"minmax"`` (each axis control are min, max, and bin) or ``"minsize"`` (each axis control are min, size and bin)
validate: if not ``None``, a function which takes tuple ``(xparams, yparams)`` of two axes parameters (each is a 3-tuple ``(min, max, bin)``)
and return their constrained versions.
"""
self.name=name
self.kind=kind
self.setObjectName(self.name)
self.setMinimumSize(QtCore.QSize(232, 83))
self.setMaximumSize(QtCore.QSize(16777215, 83))
self.gridLayout = QtWidgets.QGridLayout(self)
self.gridLayout.setObjectName("gridLayout")
self.labelROI = QtWidgets.QLabel(self)
self.labelROI.setObjectName("labelROI")
self.labelROI.setText("ROI")
self.gridLayout.addWidget(self.labelROI, 0, 0, 1, 1)
self.labelMin = QtWidgets.QLabel(self)
self.labelMin.setObjectName("labelMin")
self.labelMin.setText("Min")
self.gridLayout.addWidget(self.labelMin, 0, 1, 1, 1)
self.labelMax = QtWidgets.QLabel(self)
self.labelMax.setObjectName("labelMax")
self.labelMax.setText("Max" if kind=="minmax" else "Size")
self.gridLayout.addWidget(self.labelMax, 0, 2, 1, 1)
self.labelBin = QtWidgets.QLabel(self)
self.labelBin.setObjectName("labelBin")
self.labelBin.setText("Bin")
self.gridLayout.addWidget(self.labelBin, 0, 3, 1, 1)
self.labelX = QtWidgets.QLabel(self)
self.labelX.setObjectName("labelX")
self.labelX.setText("X")
self.gridLayout.addWidget(self.labelX, 1, 0, 1, 1)
self.labelY = QtWidgets.QLabel(self)
self.labelY.setObjectName("labelY")
self.labelY.setText("Y")
self.gridLayout.addWidget(self.labelY, 2, 0, 1, 1)
self.x_min = LVNumEdit(self)
self.x_min.setObjectName("x_min")
self.gridLayout.addWidget(self.x_min, 1, 1, 1, 1)
self.x_max = LVNumEdit(self)
self.x_max.setObjectName("x_max")
self.gridLayout.addWidget(self.x_max, 1, 2, 1, 1)
self.x_bin = LVNumEdit(self)
self.x_bin.setObjectName("x_bin")
self.gridLayout.addWidget(self.x_bin, 1, 3, 1, 1)
self.y_min = LVNumEdit(self)
self.y_min.setObjectName("y_min")
self.gridLayout.addWidget(self.y_min, 2, 1, 1, 1)
self.y_max = LVNumEdit(self)
self.y_max.setObjectName("y_max")
self.gridLayout.addWidget(self.y_max, 2, 2, 1, 1)
self.y_bin = LVNumEdit(self)
self.y_bin.setObjectName("y_bin")
self.gridLayout.addWidget(self.y_bin, 2, 3, 1, 1)
self.gridLayout.setContentsMargins(0,0,0,0)
self.gridLayout.setColumnStretch(1, 2)
self.gridLayout.setColumnStretch(2, 2)
self.gridLayout.setColumnStretch(3, 1)
self.validate=validate
for v in [self.x_min,self.y_min]:
v.set_number_format("int")
v.set_number_limit(value_type="int")
v.set_value(0)
for v in [self.x_max,self.x_bin,self.y_max,self.y_bin]:
v.set_number_format("int")
v.set_number_limit(value_type="int")
v.set_value(1)
for v in [self.x_min,self.x_max,self.x_bin,self.y_min,self.y_max,self.y_bin]:
v.value_changed.connect(self._on_edit)
self.set_limits(xlim,ylim,maxbin=maxbin,minsize=minsize,maxsize=maxsize)
[docs] def set_limits(self, xlim="keep", ylim="keep", maxbin="keep", minsize="keep", maxsize="keep"):
"""
Set limits for various parameters.
If value is ``"keep"``, keep the current value; if value is ``None``, impose no constraints.
`maxbin`, `minsize` and `maxsize` can be integers or 2-tuples depending on whether the limits are the same or different for two axes.
"""
if xlim!="keep":
self.xlim=xlim
if ylim!="keep":
self.ylim=ylim or xlim
if maxbin!="keep":
self.maxbin=maxbin
if minsize!="keep":
self.minsize=minsize
if maxsize!="keep":
self.maxsize=maxsize
for v in [self.x_min,self.x_max]:
v.set_number_limit(self.xlim[0],self.xlim[1],"coerce","int")
for v in [self.y_min,self.y_max]:
v.set_number_limit(self.ylim[0],self.ylim[1],"coerce","int")
for v in [self.x_bin,self.y_bin]:
v.set_number_limit(1,self.maxbin,"coerce","int")
self._show_values(*self.get_value())
value_changed=QtCore.pyqtSignal("PyQt_PyObject")
def _on_edit(self):
params=self.get_value()
self._show_values(*params)
self.value_changed.emit(params)
[docs] def get_value(self):
"""
Get ROI value.
Return tuple ``(xparams, yparams)`` of two axes parameters (each is a 3-tuple ``(min, max, bin)``).
"""
if self.kind=="minmax":
xparams=self.AxisParams(self.x_min.get_value(),self.x_max.get_value(),self.x_bin.get_value())
yparams=self.AxisParams(self.y_min.get_value(),self.y_max.get_value(),self.y_bin.get_value())
else:
xmin=self.x_min.get_value()
ymin=self.y_min.get_value()
xparams=self.AxisParams(xmin,xmin+self.x_max.get_value(),self.x_bin.get_value())
yparams=self.AxisParams(ymin,ymin+self.y_max.get_value(),self.y_bin.get_value())
return self.validateROI(xparams,yparams)
def _show_values(self, xparams, yparams):
if self.kind=="minmax":
xmax,ymax=xparams.max,yparams.max
else:
xmax,ymax=xparams.max-xparams.min,yparams.max-yparams.min
self.x_min.set_value(xparams.min,notify_value_change=False)
self.x_max.set_value(xmax,notify_value_change=False)
self.x_bin.set_value(xparams.bin,notify_value_change=False)
self.y_min.set_value(yparams.min,notify_value_change=False)
self.y_max.set_value(ymax,notify_value_change=False)
self.y_bin.set_value(yparams.bin,notify_value_change=False)
[docs] def set_value(self, roi, notify_value_change=True):
"""
Set ROI value.
`roi` is a tuple ``(xparams, yparams)`` of two axes parameters (each is a 3-tuple ``(min, max, bin)``).
If ``notify_value_change==True``, emit the `value_changed` signal; otherwise, change value silently.
"""
roi=self.AxisParams(*roi[0]),self.AxisParams(*roi[1])
params=self.validateROI(*roi)
self._show_values(*params)
if notify_value_change:
self.value_changed.emit(params)
[docs]class RangeCtl(QtWidgets.QWidget):
"""
Class for range control.
Can have any subset of 3 rows: specifying min-max, specifying center-span (connected to min-max), and specifying step.
Like most widgets, requires calling :meth:`setupUi` to set up before usage.
Args:
parent: parent widget
Signals:
value_changed: emitted when the ROIvalue is changed
"""
def __init__(self, parent=None):
super(RangeCtl, self).__init__(parent)
self.rng=(0,0,0)
[docs] def setupUi(self, name, lim=(None,None), order=True, formatter="float", labels=("Min","Max","Center","Span","Step"), elements=("minmax","cspan","step")):
"""
Setup the range control.
Args:
name (str): widget name
lim (tuple): limit containing min and max values
order (bool): if ``True``, first value is always smaller than the second one (values are swapped otherwise)
formatter (str): formatter for all edit boxes; see :func:`.format.as_formatter` for details
labels (tuple): tuple of 5 labels for 5 controls: min, max, center, span, and step (need to always specify 5, even if no all elements are included)
elements (tuple): tuple specifying elements which are displayed for the control;
can contain ``"minmax"`` (min-max row), ``"cspan"`` (center-span row), and ``"step"`` (step row)
"""
self.name=name
self.order=order
self.setObjectName(self.name)
# self.setMinimumSize(QtCore.QSize(232, 83))
# self.setMaximumSize(QtCore.QSize(16777215, 83))
self.gridLayout = QtWidgets.QGridLayout(self)
self.gridLayout.setObjectName("gridLayout")
row=0
if "minmax" in elements:
self.labelMin = QtWidgets.QLabel(self)
self.labelMin.setObjectName("labelMin")
self.labelMin.setText(labels[0])
self.gridLayout.addWidget(self.labelMin, row, 0, 1, 1)
self.labelMax = QtWidgets.QLabel(self)
self.labelMax.setObjectName("labelMax")
self.labelMax.setText(labels[1])
self.gridLayout.addWidget(self.labelMax, row, 2, 1, 1)
self.e_min = LVNumEdit(self)
self.e_min.setObjectName("e_min")
self.gridLayout.addWidget(self.e_min, row, 1, 1, 1)
self.e_max = LVNumEdit(self)
self.e_max.setObjectName("e_max")
self.gridLayout.addWidget(self.e_max, row, 3, 1, 1)
self.e_min.value_changed.connect(self._minmax_changed)
self.e_max.value_changed.connect(self._minmax_changed)
row+=1
else:
self.e_min=None
self.e_max=None
if "cspan" in elements:
self.labelCent = QtWidgets.QLabel(self)
self.labelCent.setObjectName("labelCent")
self.labelCent.setText(labels[2])
self.gridLayout.addWidget(self.labelCent, row, 0, 1, 1)
self.labelSpan = QtWidgets.QLabel(self)
self.labelSpan.setObjectName("labelSpan")
self.labelSpan.setText(labels[3])
self.gridLayout.addWidget(self.labelSpan, row, 2, 1, 1)
self.e_cent = LVNumEdit(self)
self.e_cent.setObjectName("e_cent")
self.gridLayout.addWidget(self.e_cent, row, 1, 1, 1)
self.e_span = LVNumEdit(self)
self.e_span.setObjectName("e_span")
self.gridLayout.addWidget(self.e_span, row, 3, 1, 1)
self.e_cent.value_changed.connect(self._cspan_changed)
self.e_span.value_changed.connect(self._cspan_changed)
row+=1
else:
self.e_cent=None
self.e_span=None
if "step" in elements:
self.labelStep = QtWidgets.QLabel(self)
self.labelStep.setObjectName("labelStep")
self.labelStep.setText(labels[4])
self.gridLayout.addWidget(self.labelStep, row, 0, 1, 1)
self.e_step = LVNumEdit(self)
self.e_step.setObjectName("e_step")
self.gridLayout.addWidget(self.e_step, row, 1, 1, 1)
self.e_step.value_changed.connect(self._step_changed)
self.e_step.set_number_limit(0,None)
row+=1
else:
self.e_step=None
self.gridLayout.setContentsMargins(2,2,2,2)
for v in [self.e_min,self.e_max,self.e_cent,self.e_span,self.e_step]:
if v:
v.change_formatter(formatter)
self._show_values(self.rng)
self.set_limit(lim)
def _limit_range(self, rng):
vmin=limit_to_range(rng[0],*self.lim)
vmax=limit_to_range(rng[1],*self.lim)
if self.order:
vmin,vmax=min(vmin,vmax),max(vmin,vmax)
step=max(0,rng[2])
return (vmin,vmax,step)
def _minmax_changed(self):
rng=self.e_min.get_value(),self.e_max.get_value(),self.rng[2]
self.set_value(rng)
def _cspan_changed(self):
cent,span=self.e_cent.get_value(),self.e_span.get_value()
rng=(cent-span/2.),(cent+span/2.),self.rng[2]
self.set_value(rng)
def _step_changed(self):
rng=self.rng[0],self.rng[1],self.e_step.get_value()
self.set_value(rng)
[docs] def set_limit(self, lim):
"""Set range values limit (2-tuple)"""
self.lim=lim
self.set_value(self.rng)
value_changed=QtCore.pyqtSignal("PyQt_PyObject")
[docs] def get_value(self):
"""Get current range value (2-tuple ``(left, right)``)"""
return self.rng
def _show_values(self, rng):
if self.e_min:
self.e_min.set_value(rng[0],notify_value_change=False)
self.e_max.set_value(rng[1],notify_value_change=False)
if self.e_cent:
self.e_cent.set_value((rng[0]+rng[1])/2.,notify_value_change=False)
self.e_span.set_value(rng[1]-rng[0],notify_value_change=False)
if self.e_step:
self.e_step.set_value(rng[2],notify_value_change=False)
[docs] def set_value(self, rng, notify_value_change=True):
"""
Get current range value
`rng` is a 2-tuple ``(left, right)``
If ``notify_value_change==True``, emit the `value_changed` signal; otherwise, change value silently.
"""
rng=self._limit_range(rng)
if self.rng!=rng:
self.rng=rng
self._show_values(rng)
if notify_value_change:
self.value_changed.emit(rng)