Source code for lavuelib.imageDisplayWidget
# Copyright (C) 2017 DESY, Christoph Rosemann, Notkestr. 85, D-22607 Hamburg
#
# lavue is an image viewing program for photon science imaging detectors.
# Its usual application is as a live viewer using hidra as data source.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation in version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# Authors:
# Christoph Rosemann <christoph.rosemann@desy.de>
# Jan Kotanski <jan.kotanski@desy.de>
#
""" image display widget """
import pyqtgraph as _pg
import numpy as np
from pyqtgraph import QtCore, QtGui
import math
import types
import logging
try:
from pyqtgraph import QtWidgets
except Exception:
from pyqtgraph import QtGui as QtWidgets
from . import axesDialog
from . import memoExportDialog
_VMAJOR, _VMINOR, _VPATCH = _pg.__version__.split(".")[:3] \
if _pg.__version__ else ("0", "9", "0")
try:
_NPATCH = int(_VPATCH)
except Exception:
_NPATCH = 0
_PQGVER = int(_VMAJOR) * 1000 + int(_VMINOR) * 100 + _NPATCH
logger = logging.getLogger("lavue")
[docs]class SafeImageItem(_pg.ImageItem):
""" Image item which caught exceptions in paint"""
def __init__(self, *args, **kargs):
""" constructor
:param args: ImageItem parameters list
:type args: :obj:`list` < :obj:`any`>
:param kargs: ImageItem parameter dictionary
:type kargs: :obj:`dict` < :obj:`str`, :obj:`any`>
"""
_pg.ImageItem.__init__(self, *args, **kargs)
[docs] def paint(self, p, *args):
""" safe paint method
:param p: painter
:type p: :class:`PyQt5.QtGui.QPainter`
:param args: ImageItem parameters list
:type args: :obj:`list` < :obj:`any`>
"""
try:
_pg.ImageItem.paint(self, p, *args)
except ValueError as e:
logger.warning(str(e))
except TypeError as e:
logger.warning(str(e))
except Exception as e:
logger.warning(str(e))
[docs]class AxesParameters(object):
""" axes parameters
"""
def __init__(self):
""" constructor
"""
#: (:obj:`bool`) enabled flag
self.enabled = False
#: (:obj:`tuple` <:obj:`float`, :obj:`float`> ) image scale (x,y)
self.scale = None
#: (:obj:`tuple` <:obj:`float`, :obj:`float`> )
# position of the first pixel
self.position = None
#: (:obj:`str`) label of x-axis
self.xtext = None
#: (:obj:`str`) label of y-axis
self.ytext = None
#: (:obj:`str`) units of x-axis
self.xunits = None
#: (:obj:`str`) units of y-axis
self.yunits = None
[docs]class IntensityParameters(object):
""" intensity parameters
"""
def __init__(self):
""" constructor
"""
#: (:obj:`bool`) do background substraction
self.dobkgsubtraction = False
#: (:obj:`bool`) do brightfield substraction
self.dobfsubtraction = False
#: (:obj:`bool`) calculate statistics without scaling
self.statswoscaling = True
#: (:obj:`str`) intensity scaling
self.scaling = "sqrt"
[docs]class TransformationParameters(object):
""" transformation parameters
"""
def __init__(self):
""" constructor
"""
#: (:obj:`bool`) transpose coordinates flag
self.transpose = False
#: (:obj:`bool`) left-right flip coordinates flag
self.leftrightflip = False
#: (:obj:`bool`) up-down flip coordinates flag
self.updownflip = False
#: (:obj:`bool`) transpose coordinates flag
self.orgtranspose = False
[docs]class ImageDisplayWidget(_pg.GraphicsLayoutWidget):
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) aspect locked toggled signal
aspectLockedToggled = QtCore.pyqtSignal(bool)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) mouse position changed signal
mouseImagePositionChanged = QtCore.pyqtSignal()
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) mouse double clicked
mouseImageDoubleClicked = QtCore.pyqtSignal(float, float)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) mouse single clicked
mouseImageSingleClicked = QtCore.pyqtSignal(float, float)
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
_pg.GraphicsLayoutWidget.__init__(self, parent)
#: (:class:`PyQt5.QtWidgets.QLayout`) the main layout
self.__layout = self.ci
#: (:class:`lavuelib.imageDisplayWidget.AxesParameters`)
#: axes parameters
self.__axes = AxesParameters()
#: (:class:`lavuelib.imageDisplayWidget.AxesParameters`)
#: down-sampling and range window axes parameters
self.__wraxes = AxesParameters()
#: (:class:`lavuelib.imageDisplayWidget.AxesParameters`)
#: tool axes parameters
self.__toolaxes = AxesParameters()
#: (:class:`lavuelib.imageDisplayWidget.IntensityParameters`)
#: intensity parameters
self.__intensity = IntensityParameters()
#: (:class:`lavuelib.imageDisplayWidget.TransformationParameters`)
#: intensity parameters
self.__transformations = TransformationParameters()
#: (:class:`numpy.ndarray`) data to displayed in 2d widget
self.__data = None
#: (:class:`numpy.ndarray`) raw data to cut plots
self.__rawdata = None
#: (:obj:`list` < :class:`pyqtgraph.ImageItem` >) image item list
self.__images = [SafeImageItem(), SafeImageItem(), SafeImageItem()]
#: (:class:`pyqtgraph.ImageItem`) image item
self.__image = self.__images[0]
#: (:class:`pyqtgraph.ViewBox`) viewbox item
self.__viewbox = self.__layout.addViewBox(row=0, col=1)
self.__viewbox.addItem(self.__images[0])
self.__viewbox.addItem(self.__images[1])
self.__viewbox.addItem(self.__images[2])
self.__images[1].hide()
self.__images[2].hide()
#: (:obj:`float`) current floar x-position
self.__xfdata = 0
#: (:obj:`float`) current floar y-position
self.__yfdata = 0
#: (:obj:`float`) current x-position
self.__xdata = 0
#: (:obj:`float`) current y-position
self.__ydata = 0
#: (:obj:`bool`) auto display level flag
self.__autodisplaylevels = 2
#: (:obj:`bool`) auto down sample
self.__autodownsample = True
#: ([:obj:`float`, :obj:`float`]) minimum and maximum intensity levels
self.__displaylevels = [None, None]
#: (:obj: `list` < [:obj:`float`, :obj:`float`] >) channel levels
self.__channellevels = None
#: (:obj:`bool`) lock for double click
self.__doubleclicklock = False
#: (:obj:`bool`) rgb on flag
self.__rgb = False
#: (:obj:`bool`) gradient colors flag
self.__gradientcolors = False
#: (:obj:`str`) levelmode
self.__levelmode = 'mono'
#: (:obj:`dict` < :obj:`str`, :obj:`DisplayExtension` >)
# extension dictionary with name keys
self.__extensions = {}
#: (:class:`PyQt5.QtWidgets.QAction`) set aspect ration locked action
self.__setaspectlocked = QtWidgets.QAction(
"Set Aspect Locked", self.__viewbox.menu)
self.__setaspectlocked.setCheckable(True)
if _PQGVER < 1009:
self.__viewbox.menu.axes.insert(0, self.__setaspectlocked)
self.__viewbox.menu.addAction(self.__setaspectlocked)
#: (:class:`PyQt5.QtWidgets.QAction`) view one to one pixel action
self.__viewonetoone = QtWidgets.QAction(
"View 1:1 pixels", self.__viewbox.menu)
self.__viewonetoone.triggered.connect(self._oneToOneRange)
if _PQGVER < 1009:
self.__viewbox.menu.axes.insert(0, self.__viewonetoone)
self.__viewbox.menu.addAction(self.__viewonetoone)
#: (:class:`pyqtgraph.AxisItem`) left axis
self.__leftaxis = _pg.AxisItem('left')
#: (:class:`pyqtgraph.AxisItem`) bottom axis
self.__bottomaxis = _pg.AxisItem('bottom')
self.__leftaxis.linkToView(self.__viewbox)
self.__layout.addItem(self.__leftaxis, row=0, col=0)
self.__bottomaxis.linkToView(self.__viewbox)
self.__layout.addItem(self.__bottomaxis, row=1, col=1)
self.sceneObj.sigMouseMoved.connect(self.mouse_position)
self.sceneObj.sigMouseClicked.connect(self.mouse_click)
self.__setaspectlocked.triggered.connect(self.emitAspectLockedToggled)
self.sceneObj.contextMenu[0].triggered.disconnect(
self.sceneObj.showExportDialog)
self.sceneObj.showExportDialog = types.MethodType(
memoExportDialog.GraphicsScene_showExportDialog, self.sceneObj)
self.sceneObj.contextMenu[0].triggered.connect(
self.sceneObj.showExportDialog)
self.sceneObj.rawdata = None
self.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
[docs] def viewbox(self):
"""provides viewbox
:rtype: :class:`pyqtgraph.ViewBox`
:returns: viewbox
"""
return self.__viewbox
[docs] def addExtensions(self, extlist):
"""provides viewbox
:param extlist: extension list
:type extlist: :obj:`list` < :class:`DisplayExtension` >
"""
for excls in extlist:
ext = excls(self)
self.__extensions[ext.name] = ext
[docs] def extension(self, name):
"""provides viewbox
:param name: extension name
:type name: :obj:`str`
:rtype: :class:`DisplayExtension`
:returns: display extension
"""
return self.__extensions[name]
[docs] def extensions(self):
"""provides extension names
:rtype: :obj:`list`
:returns: extension names
"""
return self.__extensions.keys()
[docs] def setAspectLocked(self, flag):
"""sets aspectLocked
:param status: state to set
:type status: :obj:`bool`
:returns: old state
:rtype: :obj:`bool`
"""
if flag != self.__setaspectlocked.isChecked():
self.__setaspectlocked.setChecked(flag)
oldflag = self.__viewbox.state["aspectLocked"]
self.__viewbox.setAspectLocked(flag)
return oldflag
def _oneToOneRange(self):
""" set one to one range
"""
ps = self.__image.pixelSize()
currange = self.__viewbox.viewRange()
xrg = currange[0][1] - currange[0][0]
yrg = currange[1][1] - currange[1][0]
if self.__axes.position is not None and self.__axes.enabled:
self.__viewbox.setRange(
QtCore.QRectF(
self.__axes.position[0], self.__axes.position[1],
xrg * ps[0], yrg * ps[1]),
padding=0)
elif self.__wraxes.position is not None and self.__wraxes.enabled:
self.__viewbox.setRange(
QtCore.QRectF(
self.__wraxes.position[0],
self.__wraxes.position[1],
xrg * ps[0], yrg * ps[1]),
padding=0)
else:
self.__viewbox.setRange(
QtCore.QRectF(0, 0, xrg * ps[0], yrg * ps[1]),
padding=0)
if self.__setaspectlocked.isChecked():
self.__setaspectlocked.setChecked(False)
self.__setaspectlocked.triggered.emit(False)
[docs] def setViewRange(self, rangelist):
""" set view range values
:param rangelist: xmin,ymin,xsize,ysize
:type rangelist: :obj:`str`
"""
lims = rangelist.split(",")
if lims and len(lims) == 4:
fl = [float(lm.replace("m", "-")) for lm in lims]
self.__viewbox.setRange(QtCore.QRectF(*fl), padding=0)
[docs] def viewRange(self):
""" get view range values
:returns: xmin,ymin,xsize,ysize
:rtype rangelist: :obj:`str`
"""
vr = self.__viewbox.viewRange()
vr0 = vr[0]
vr1 = vr[1]
return "%s,%s,%s,%s" % (
vr0[0], vr1[0], vr0[1] - vr0[0], vr1[1] - vr1[0])
def __setScale(self, position=None, scale=None, update=True, tool=False,
force=False, wrenabled=None, wrupdate=True):
""" set axes scales
:param position: start position of axes
:type position: [:obj:`float`, :obj:`float`]
:param scale: scale axes
:type scale: [:obj:`float`, :obj:`float`]
:param update: update scales on image
:type update: :obj:`bool`
:param tool: update tool scale
:type tool: :obj:`bool`
:param force: force rescaling
:type force: :obj:`bool`
:param wrenabled: down-sampling rescale
:type wrenabled: :obj:`bool`
:param wrupdate: update window ranges
:type wrupdate: :obj:`bool`
"""
if wrenabled is not None:
self.__wraxes.enabled = wrenabled
else:
wrenabled = self.__wraxes.enabled
if tool:
axes = self.__toolaxes
elif self.__axes.enabled:
axes = self.__axes
else:
axes = self.__wraxes
anyupdate = update or wrenabled
if update:
self.__setLabels(axes.xtext, axes.ytext,
axes.xunits, axes.yunits)
if not force:
if axes.position == position and axes.scale == scale and \
position is None and scale is None:
return
axes.position = position
axes.scale = scale
if wrupdate and not tool:
self.__wraxes.position = position
self.__wraxes.scale = scale
self.__axes.position = position
self.__axes.scale = scale
else:
axes.position = position
axes.scale = scale
for image in self.__images:
image.resetTransform()
if axes.scale is not None and anyupdate:
if not self.__transformations.transpose:
self._imagescale(*axes.scale)
else:
self._imagescale(
axes.scale[1], axes.scale[0])
else:
self._imagescale(1, 1)
for image in self.__images:
if axes.position is not None and anyupdate:
if self.__transformations.orgtranspose and wrenabled:
image.setPos(
axes.position[1], axes.position[0])
elif not self.__transformations.transpose:
image.setPos(*axes.position)
else:
image.setPos(
axes.position[1], axes.position[0])
else:
image.setPos(0, 0)
if self.sceneObj.rawdata is not None and update:
self.autoRange()
def _imagescale(self, x, y):
""" set image scale x,y
:param x: x pixel coordinate
:type x: float
:param y: y pixel coordinate
:type y: float
"""
for image in self.__images:
try:
tr = image.transform()
tr.scale(x, y)
image.setTransform(tr)
except Exception:
image.scale(x, y)
[docs] def setToolScale(self, position=None, scale=None):
""" set axes scales
:param position: start position of axes
:type position: [:obj:`float`, :obj:`float`]
:param scale: scale axes
:type scale: [:obj:`float`, :obj:`float`]
:param update: update scales on image
:type updatescale: :obj:`bool`
"""
self.__toolaxes.position = position
self.__toolaxes.scale = scale
[docs] def scale(self, useraxes=True, noNone=False):
""" provides scale and position of the axes
:param useraxes: use user scaling
:type useraxes: :obj:`bool`
:param noNone: return values without None
:type noNone: :obj:`bool`
:rtype: [int, int, int, int]
:returns: [posx, posy, scalex, scaley]
"""
if noNone:
position = 0, 0
scale = 1, 1
else:
position = None, None
scale = None, None
if self.__axes.scale is not None and \
self.__axes.enabled is True and useraxes:
position = [0, 0] \
if self.__axes.position is None \
else self.__axes.position
scale = self.__axes.scale
elif self.__wraxes.scale is not None and self.__wraxes.enabled is True:
position = [0, 0] \
if self.__wraxes.position is None \
else self.__wraxes.position
scale = self.__wraxes.scale
return position[0], position[1], scale[0], scale[1]
def __resetScale(self, tool=False):
""" reset axes scales
:param tool: update tool scale
:type tool: :obj:`bool`
"""
if tool:
axes = self.__toolaxes
elif self.__axes.enabled:
axes = self.__axes
else:
axes = self.__wraxes
for image in self.__images:
if axes.scale is not None or axes.position is not None:
image.resetTransform()
if axes.scale is not None:
self._imagescale(1, 1)
for image in self.__images:
if axes.position is not None:
image.setPos(0, 0)
if axes.scale is not None or axes.position is not None:
if self.sceneObj.rawdata is not None:
self.autoRange()
self.__setLabels()
[docs] def updateImage(self, img=None, rawimg=None):
""" updates the image to display
:param img: 2d image array
:type img: :class:`numpy.ndarray`
:param rawimg: 2d raw image array
:type rawimg: :class:`numpy.ndarray`
"""
try:
if img is not None and len(img.shape) == 3:
if self.__gradientcolors:
# self.__image.setLookupTable(None)
# if img.dtype.kind == 'f' and np.isnan(img.min()):
# img = np.nan_to_num(img)
for iid, image in enumerate(self.__images):
if iid < img.shape[2]:
if np.isnan(img[:, :, iid]).all():
image.hide()
else:
image.show()
if self.__channellevels and \
self.levelMode() != 'mono':
image.setImage(
img[:, :, iid],
levels=self.__channellevels[iid],
autoLevels=False)
elif self.__displaylevels[0] is not None \
and self.__displaylevels[1] \
is not None:
image.setImage(
img[:, :, iid],
levels=self.__displaylevels,
autoLevels=False)
else:
image.setImage(
img[:, :, iid],
# levels=[[0,255], [0, 255], [0, 255]],
autoLevels=False)
else:
self._hideimages()
self.__image.show()
# if uncomented the gradientColors does not work
# self.__image.setLookupTable(None)
if img.dtype.kind == 'f' and np.isnan(img.min()):
img = np.nan_to_num(img)
if self.__channellevels and self.levelMode() != 'mono':
self.__image.setImage(
img,
# lut=None,
levels=self.__channellevels,
autoLevels=False)
elif self.__displaylevels[0] is not None \
and self.__displaylevels[1] is not None:
self.__image.setImage(
img,
# lut=None,
levels=self.__displaylevels,
autoLevels=False)
else:
self.__image.setImage(
img,
# lut=None,
# levels=[[0,255], [0, 255], [0, 255]],
autoLevels=False)
elif (self.__autodisplaylevels
and self.__displaylevels[0] is not None
and self.__displaylevels[1] is not None):
self._hideimages()
self.__image.show()
self.__image.setImage(
img, autoLevels=False,
levels=self.__displaylevels,
autoDownsample=self.__autodownsample)
elif (self.__autodisplaylevels
or self.__displaylevels[0] is None
or self.__displaylevels[1] is None):
self._hideimages()
self.__image.show()
self.__image.setImage(
img, autoLevels=False,
autoDownsample=self.__autodownsample)
else:
self._hideimages()
self.__image.show()
self.__image.setImage(
img, autoLevels=False,
levels=self.__displaylevels,
autoDownsample=self.__autodownsample)
except Exception as e:
logger.warning(str(e))
# print(str(e))
self.__data = img
self.sceneObj.rawdata = rawimg
self.mouse_position()
[docs] def currentIntensity(self):
""" provides intensity for current mouse position
:returns: (x position, y position, pixel intensity,
x position, y position)
:rtype: (float, float, float, float, float)
"""
xfdata = None
yfdata = None
for ext in self.__extensions.values():
if ext.enabled():
coords = ext.coordinates()
xfdata = coords[0]
yfdata = coords[1]
if xfdata is None or yfdata is None:
xfdata = self.__xfdata
yfdata = self.__yfdata
if self.sceneObj.rawdata is not None:
try:
if not self.__transformations.transpose:
xf = int(xfdata)
yf = int(yfdata)
else:
yf = int(xfdata)
xf = int(yfdata)
if xf >= 0 and yf >= 0 \
and xf < self.sceneObj.rawdata.shape[0] \
and yf < self.sceneObj.rawdata.shape[1]:
intensity = self.sceneObj.rawdata[xf, yf]
else:
intensity = 0.
except Exception:
intensity = 0.
else:
intensity = 0.
return (xfdata, yfdata, intensity,
self.__xdata, self.__ydata)
[docs] def scalingLabel(self):
""" provides scaling label
:returns: scaling label
:rtype: str
"""
ilabel = None
for ext in self.__extensions.values():
if ext.enabled():
ilabel = ext.scalingLabel()
if ilabel is None:
scaling = self.__intensity.scaling \
if not self.__intensity.statswoscaling else "linear"
if self.__intensity.dobkgsubtraction and \
not self.__intensity.dobfsubtraction:
ilabel = "%s(intensity-background)" % (
scaling if scaling != "linear" else "")
elif (self.__intensity.dobkgsubtraction and
self.__intensity.dobfsubtraction):
ilabel = "%s((intensity-DF)/(BF-DF))" % (
scaling if scaling != "linear" else "")
elif (not self.__intensity.dobkgsubtraction and
self.__intensity.dobfsubtraction):
ilabel = "%s(intensity/BF)" % (
scaling if scaling != "linear" else "")
else:
if scaling == "linear":
ilabel = "intensity"
else:
ilabel = "%s(intensity)" % scaling
return ilabel
[docs] def scaling(self):
""" provides scaling type
:returns: scaling type
:rtype: str
"""
return self.__intensity.scaling
[docs] def axesunits(self):
""" return axes units
:returns: x,y units
:rtype: (:obj:`str`, :obj:`str`)
"""
return (self.__axes.xunits, self.__axes.yunits)
[docs] def axestext(self):
""" return axes text
:returns: x,y text
:rtype: (:obj:`str`, :obj:`str`)
"""
return (self.__axes.xtext, self.__axes.ytext)
[docs] def scaledxy(self, x, y, useraxes=True):
""" provides scaled x,y positions
:param x: x pixel coordinate
:type x: float
:param y: y pixel coordinate
:type y: float
:param useraxes: use user scaling
:type useraxes: :obj:`bool`
:returns: scaled x,y position
:rtype: (float, float)
"""
txdata = None
tydata = None
if self.__axes.enabled and useraxes:
axes = self.__axes
elif self.__wraxes.enabled:
axes = self.__wraxes
else:
return None, None
if axes.scale is not None:
txdata = x * axes.scale[0]
tydata = y * axes.scale[1]
if axes.position is not None:
txdata = txdata + self.__axes.position[0]
tydata = tydata + self.__axes.position[1]
elif axes.position is not None:
txdata = x + axes.position[0]
tydata = y + axes.position[1]
return (txdata, tydata)
[docs] def descaledxy(self, x, y, useraxes=True):
""" provides scaled x,y positions
:param x: x pixel coordinate
:type x: float
:param y: y pixel coordinate
:type y: float
:param useraxes: use user scaling
:type useraxes: :obj:`bool`
:returns: scaled x,y position
:rtype: (float, float)
"""
txdata = None
tydata = None
if self.__axes.enabled and useraxes:
axes = self.__axes
elif self.__wraxes.enabled:
axes = self.__wraxes
else:
return None, None
if axes.position is not None:
txdata = x - axes.position[0]
tydata = y - axes.position[1]
if axes.scale is not None:
txdata = txdata / axes.scale[0]
tydata = tydata / axes.scale[1]
elif axes.scale is not None:
txdata = x / axes.scale[0]
tydata = y / axes.scale[1]
return (txdata, tydata)
[docs] @QtCore.pyqtSlot(object)
def mouse_position(self, event=None):
""" updates image widget after mouse position change
:param event: mouse move event
:type event: :class:`pyqtgraph.QtCore.QEvent`
"""
try:
if event is not None:
mousePoint = self.__image.mapFromScene(event)
if not self.__transformations.transpose:
self.__xdata = mousePoint.x()
self.__ydata = mousePoint.y()
else:
self.__ydata = mousePoint.x()
self.__xdata = mousePoint.y()
self.__xfdata = math.floor(self.__xdata)
self.__yfdata = math.floor(self.__ydata)
for ext in self.__extensions.values():
if ext.enabled():
ext.mouse_position(self.__xdata, self.__ydata)
self.mouseImagePositionChanged.emit()
except Exception:
# print("Warning: %s" % str(e))
pass
def __setLabels(self, xtext=None, ytext=None, xunits=None, yunits=None):
""" sets labels and units
:param xtext: x-label text
:param type: :obj:`str`
:param ytext: y-label text
:param type: :obj:`str`
:param xunits: x-units text
:param type: :obj:`str`
:param yunits: y-units text
:param type: :obj:`str`
"""
self.__bottomaxis.autoSIPrefix = False
self.__leftaxis.autoSIPrefix = False
if not self.__transformations.transpose:
self.__bottomaxis.setLabel(text=xtext, units=xunits)
self.__leftaxis.setLabel(text=ytext, units=yunits)
if xunits is None:
self.__bottomaxis.labelUnits = ''
if yunits is None:
self.__leftaxis.labelUnits = ''
if xtext is None:
self.__bottomaxis.label.setVisible(False)
if ytext is None:
self.__leftaxis.label.setVisible(False)
else:
self.__bottomaxis.setLabel(text=ytext, units=yunits)
self.__leftaxis.setLabel(text=xtext, units=xunits)
if yunits is None:
self.__bottomaxis.labelUnits = ''
if xunits is None:
self.__leftaxis.labelUnits = ''
if ytext is None:
self.__bottomaxis.label.setVisible(False)
if xtext is None:
self.__leftaxis.label.setVisible(False)
[docs] @QtCore.pyqtSlot(object)
def mouse_click(self, event):
""" updates image widget after mouse click
:param event: mouse click event
:type event: :class:`pyqtgraph.QtCore.QEvent`
"""
mousePoint = self.__image.mapFromScene(event.scenePos())
if not self.__transformations.transpose:
xdata = mousePoint.x()
ydata = mousePoint.y()
else:
ydata = mousePoint.x()
xdata = mousePoint.y()
# if double click: fix mouse crosshair
# another double click releases the crosshair again
if event.double():
for ext in self.__extensions.values():
if ext.enabled():
ext.mouse_doubleclick(
xdata, ydata,
self.__doubleclicklock)
self.mouseImageDoubleClicked.emit(xdata, ydata)
else:
for ext in self.__extensions.values():
if ext.enabled():
ext.mouse_click(xdata, ydata)
self.mouseImageSingleClicked.emit(xdata, ydata)
[docs] def setAutoLevels(self, autolevels):
""" sets auto levels
:param autolevels: auto levels enabled
:type autolevels: :obj:`bool`
"""
if autolevels == 2:
self.__autodisplaylevels = True
else:
self.__autodisplaylevels = False
[docs] def setAutoDownSample(self, autodownsample):
""" sets auto levels
:param autolevels: auto down sample enabled
:type autolevels: :obj:`bool`
"""
if autodownsample:
self.__autodownsample = True
else:
self.__autodownsample = False
[docs] def setDisplayMinLevel(self, level=None):
""" sets minimum intensity level
:param level: minimum intensity
:type level: :obj:`float`
"""
if level is not None:
self.__displaylevels[0] = level
[docs] def setDisplayMaxLevel(self, level=None):
""" sets maximum intensity level
:param level: maximum intensity
:type level: :obj:`float`
"""
if level is not None:
self.__displaylevels[1] = level
[docs] def setDisplayChannelLevels(self, levels=None):
""" sets maximum intensity levels
:param levels: channel intensity levels
:type levels: :obj:`list` < (:obj`float`:, :obj`float`:)>
"""
if levels is not None:
self.__channellevels = levels
[docs] def setDoubleClickLock(self, status=True):
""" sets double click lock
:param status: status flag
:type status: :obj:`bool`
"""
self.__doubleclicklock = status
[docs] def setSubWidgets(self, parameters):
""" set subwidget properties
:param parameters: tool parameters
:type parameters: :class:`lavuelib.toolWidget.ToolParameters`
"""
rescale = False
doreset = False
if parameters.scale is not None:
if parameters.scale is False:
doreset = self.__axes.enabled
self.__axes.enabled = parameters.scale
# self.__wraxes.enabled = parameters.scale
if parameters.toolscale is not None:
doreset = doreset or parameters.toolscale
if self.__toolaxes.enabled and not parameters.toolscale:
doreset = True
rescale = True
self.__toolaxes.enabled = parameters.toolscale
for ext in self.__extensions.values():
ext.show(parameters)
if doreset:
self.__resetScale(tool=parameters.toolscale)
if self.__wraxes.enabled:
self.__setScale(
self.__wraxes.position, self.__wraxes.scale, force=rescale)
if parameters.scale is True or rescale:
self.__setScale(
self.__axes.position, self.__axes.scale, force=rescale)
if parameters.toolscale is True:
self.__setScale(
self.__toolaxes.position, self.__toolaxes.scale, tool=True)
[docs] @QtCore.pyqtSlot(bool)
def emitAspectLockedToggled(self, status):
""" emits aspectLockedToggled
:param status: aspectLockedToggled status
:type status: :obj:`bool`
"""
self.aspectLockedToggled.emit(status)
[docs] def setTicks(self):
""" launch axes widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = axesDialog.AxesDialog()
if self.__axes.position is None:
cnfdlg.xposition = None
cnfdlg.yposition = None
else:
cnfdlg.xposition = self.__axes.position[0]
cnfdlg.yposition = self.__axes.position[1]
if self.__axes.scale is None:
cnfdlg.xscale = None
cnfdlg.yscale = None
else:
cnfdlg.xscale = self.__axes.scale[0]
cnfdlg.yscale = self.__axes.scale[1]
cnfdlg.xtext = self.__axes.xtext
cnfdlg.ytext = self.__axes.ytext
cnfdlg.xunits = self.__axes.xunits
cnfdlg.yunits = self.__axes.yunits
cnfdlg.createGUI()
if cnfdlg.exec_():
if cnfdlg.xposition is not None and cnfdlg.yposition is not None:
position = tuple([cnfdlg.xposition, cnfdlg.yposition])
else:
position = None
if cnfdlg.xscale is not None and cnfdlg.yscale is not None:
scale = tuple([cnfdlg.xscale, cnfdlg.yscale])
else:
scale = None
self.__axes.xtext = cnfdlg.xtext or None
self.__axes.ytext = cnfdlg.ytext or None
self.__axes.xunits = cnfdlg.xunits or None
self.__axes.yunits = cnfdlg.yunits or None
self.__setScale(position, scale, wrupdate=False)
self.updateImage(self.__data, self.sceneObj.rawdata)
return True
return False
[docs] def updateTicks(self, record):
""" update Ticks values
:param record: dict record with the tick parameters:
"position" : [x, y]
"scale" : [sx, sy]
"xtext" : xlabel
"ytext" : ylabel
"xunits" : xunits
"yunits" : yunits
:type record: :obj:`dict`<:obj:`str`, `any`>
"""
update = False
if "xtext" in record.keys():
self.__axes.xtext = str(record["xtext"] or "")
update = True
if "ytext" in record.keys():
self.__axes.ytext = str(record["ytext"] or "")
update = True
if "xunits" in record.keys():
self.__axes.xunits = str(record["xunits"] or "")
update = True
if "yunits" in record.keys():
self.__axes.yunits = str(record["yunits"] or "")
update = True
if "position" in record.keys() or "scale" in record.keys():
update = True
if "scale" not in record.keys():
scale = self.__axes.scale
else:
scale = record["scale"]
if "position" not in record.keys():
position = self.__axes.position
else:
position = record["position"]
try:
self.__setScale(position, scale, wrupdate=False)
except Exception as e:
logger.warning(str(e))
# print(str(e))
if update:
self.updateImage(self.__data, self.sceneObj.rawdata)
[docs] def rawData(self):
""" provides the raw data
:returns: current raw data
:rtype: :class:`numpy.ndarray`
"""
return self.sceneObj.rawdata
[docs] def currentData(self):
""" provides the data
:returns: current data
:rtype: :class:`numpy.ndarray`
"""
return self.__data
[docs] def updateMetaData(self, axisscales=None, axislabels=None,
rescale=False):
""" update Metadata informations
:param axisscales: [xstart, ystart, xscale, yscale]
:type axisscales:
[:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`]
:param axislabels: [xtext, ytext, xunits, yunits]
:type axislabels:
[:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`]
:param rescale: rescale or select range window
:type rescale: :obj:`True`
"""
if axislabels is not None:
self.__axes.xtext = str(axislabels[0]) \
if axislabels[0] is not None else None
self.__axes.ytext = str(axislabels[1]) \
if axislabels[1] is not None else None
self.__axes.xunits = str(axislabels[2]) \
if axislabels[2] is not None else None
self.__axes.yunits = str(axislabels[3]) \
if axislabels[3] is not None else None
position = None
scale = None
if axisscales is not None:
try:
if axisscales[0] is None and axisscales[1] is not None:
axisscales[0] = 0
if axisscales[1] is None and axisscales[0] is not None:
axisscales[1] = 0
position = (float(axisscales[0]), float(axisscales[1]))
except Exception:
position = None
try:
scale = (float(axisscales[2]), float(axisscales[3]))
except Exception:
scale = None
self.__setScale(position, scale,
self.__axes.enabled,
wrenabled=rescale)
[docs] def setStatsWOScaling(self, status):
""" sets statistics without scaling flag
:param status: statistics without scaling flag
:type status: :obj:`bool`
:returns: change status
:rtype: :obj:`bool`
"""
if self.__intensity.statswoscaling != status:
self.__intensity.statswoscaling = status
return True
return False
[docs] def setScalingType(self, scalingtype):
""" sets intensity scaling types
:param scalingtype: intensity scaling type
:type scalingtype: :obj:`str`
"""
self.__intensity.scaling = scalingtype
[docs] def setDoBkgSubtraction(self, state):
""" sets do background subtraction flag
:param status: do background subtraction flag
:type status: :obj:`bool`
"""
self.__intensity.dobkgsubtraction = state
[docs] def setDoBFSubtraction(self, state):
""" sets do brightfield subtraction flag
:param status: do brightfield subtraction flag
:type status: :obj:`bool`
"""
self.__intensity.dobfsubtraction = state
[docs] def image(self, iid=0):
""" provides imageItem object
:param iid: image id
:type iid: :obj:`int`
:returns: image object
:rtype: :class:`pyqtgraph.imageItem.ImageItem`
"""
return self.__images[iid]
[docs] def setTransformations(self, transpose, leftrightflip, updownflip,
orgtranspose):
""" sets coordinate transformations
:param transpose: transpose coordinates flag
:type transpose: :obj:`bool`
:param leftrightflip: left-right flip coordinates flag
:type leftrightflip: :obj:`bool`
:param updownflip: up-down flip coordinates flag
:type updownflip: :obj:`bool`
:param orgtranspose: selected transpose coordinates flag
:type orgtranspose: :obj:`bool`
"""
if self.__transformations.transpose != transpose:
self.__transformations.transpose = transpose
self.__transposeItems()
if self.__transformations.orgtranspose != orgtranspose:
self.__transformations.orgtranspose = orgtranspose
if self.__transformations.leftrightflip != leftrightflip:
self.__transformations.leftrightflip = leftrightflip
if hasattr(self.__viewbox, "invertX"):
self.__viewbox.invertX(leftrightflip)
else:
""" version 0.9.10 without invertX """
# workaround for a bug in old pyqtgraph versions: stretch 0.10
self.__viewbox.sigXRangeChanged.emit(
self.__viewbox, tuple(self.__viewbox.state['viewRange'][0]))
self.__viewbox.sigYRangeChanged.emit(
self.__viewbox, tuple(self.__viewbox.state['viewRange'][1]))
if _PQGVER < 1202:
self.__viewbox.sigRangeChanged.emit(
self.__viewbox, self.__viewbox.state['viewRange'])
else:
self.__viewbox.sigRangeChanged.emit(
self.__viewbox, self.__viewbox.state['viewRange'],
[True, True])
if self.__transformations.updownflip != updownflip:
self.__transformations.updownflip = updownflip
self.__viewbox.invertY(updownflip)
# workaround for a bug in old pyqtgraph versions: stretch 0.9.10
self.__viewbox.sigXRangeChanged.emit(
self.__viewbox, tuple(self.__viewbox.state['viewRange'][0]))
self.__viewbox.sigYRangeChanged.emit(
self.__viewbox, tuple(self.__viewbox.state['viewRange'][1]))
if _PQGVER < 1202:
self.__viewbox.sigRangeChanged.emit(
self.__viewbox, self.__viewbox.state['viewRange'])
else:
self.__viewbox.sigRangeChanged.emit(
self.__viewbox, self.__viewbox.state['viewRange'],
[True, True])
[docs] def transformations(self):
""" povides coordinates transformations
:returns: transpose, leftrightflip, updownflip flags,
original transpose
:rtype: (:obj:`bool`, :obj:`bool`, :obj:`bool`)
"""
return (
self.__transformations.transpose,
self.__transformations.leftrightflip,
self.__transformations.updownflip,
self.__transformations.orgtranspose
)
def __transposeItems(self):
""" transposes all image items
"""
for ext in self.__extensions.values():
ext.transpose()
self.__transposeAxes()
def __transposeAxes(self):
""" transposes axes
"""
if self.__axes.enabled is True:
self.__setScale(self.__axes.position, self.__axes.scale)
elif self.__wraxes.enabled is True:
self.__setScale(self.__wraxes.position,
self.__wraxes.scale)
if self.__toolaxes.enabled is True:
self.__setScale(
self.__toolaxes.position, self.__toolaxes.scale, tool=True)
[docs] def autoRange(self):
""" sets auto range
"""
try:
self.__viewbox.autoRange()
self.__viewbox.enableAutoRange('xy', True)
except Exception as e:
logger.warning(str(e))
# print(str(e))
[docs] def rangeWindowEnabled(self):
""" provide info if range window enabled
:returns: range window enabled
:rtype: :obj:`bool`
"""
return self.__wraxes.enabled
[docs] def rangeWindowScale(self):
""" provide info about range window sclae
:returns: range window scale
:rtype: :obj:`float`
"""
if self.__wraxes.enabled and self.__wraxes.scale \
and self.__wraxes.scale[0] > 1:
return self.__wraxes.scale[0]
else:
return 1.0
[docs] def setrgb(self, status=True):
""" sets RGB on/off
:param status: True for on and False for off
:type status: :obj:`bool`
"""
self.__rgb = status
[docs] def rgb(self):
""" gets RGB on/off
:returns: True for on and False for off
:rtype: :obj:`bool`
"""
return self.__rgb
def _hideimages(self):
""" hides additional images
"""
self.__images[1].hide()
self.__images[2].hide()
[docs] def setGradientColors(self, status=True):
""" sets gradientcolors on/off
:param status: True for on and False for off
:type status: :obj:`bool`
"""
if not status:
self._hideimages()
self.__gradientcolors = status
[docs] def gradientColors(self):
""" gets gradientcolors on/off
:returns: True for on and False for off
:rtype: :obj:`bool`
"""
return self.__gradientcolors
[docs] def setLevelMode(self, levelmode=True):
""" sets levelmode
:param levelmode: level mode, i.e. `mono` or `rgba`
:type levelmode: :obj:`str`
"""
self.__levelmode = levelmode
[docs] def levelMode(self):
""" gets level mode
:returns: level mode, i.e. `mono` or `rgba`
:rtype: :obj:`str`
"""
return self.__levelmode