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
import json

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()] #: (:obj:`list` < :class:`pyqtgraph.ImageItem` >) mask image item list self.__maskImages = [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() self.__viewbox.addItem(self.__maskImages[0]) self.__viewbox.addItem(self.__maskImages[1]) self.__viewbox.addItem(self.__maskImages[2]) self.__maskImages[0].hide() self.__maskImages[1].hide() self.__maskImages[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:`tuple` <:obj:`int`>) mask color self.__overflowcolor = (255, 255, 255) #: (: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) self.__viewbox.menu.ctrl[0].invertCheck.hide() self.__viewbox.menu.ctrl[0].mouseCheck.hide() self.__viewbox.menu.ctrl[0].linkCombo.hide() self.__viewbox.menu.ctrl[0].autoPanCheck.hide() self.__viewbox.menu.ctrl[0].visibleOnlyCheck.hide() self.__viewbox.menu.ctrl[0].label.hide() self.__viewbox.menu.ctrl[1].invertCheck.hide() self.__viewbox.menu.ctrl[1].mouseCheck.hide() self.__viewbox.menu.ctrl[1].linkCombo.hide() self.__viewbox.menu.ctrl[1].autoPanCheck.hide() self.__viewbox.menu.ctrl[1].visibleOnlyCheck.hide() self.__viewbox.menu.ctrl[1].label.hide() #: (: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 setOverflowColor(self, color): """ sets item color :param color: json list of overflow color :type color: :obj:`str` """ try: # print("COL", color) self.__overflowcolor = tuple(json.loads(color)) except Exception: self.__overflowcolor = (255, 255, 255)
[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() for image in self.__maskImages: 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 iid, image in enumerate(self.__images): if axes.position is not None and anyupdate: if self.__transformations.orgtranspose and wrenabled: image.setPos( axes.position[1], axes.position[0]) self.__maskImages[iid].setPos( axes.position[1], axes.position[0]) elif not self.__transformations.transpose: image.setPos(*axes.position) self.__maskImages[iid].setPos(*axes.position) else: image.setPos( axes.position[1], axes.position[0]) self.__maskImages[iid].setPos( axes.position[1], axes.position[0]) else: image.setPos(0, 0) self.__maskImages[iid].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 iid, image in enumerate(self.__images): try: tr = image.transform() tr.scale(x, y) image.setTransform(tr) self.__maskImages[iid].setTransform(tr) except Exception: image.scale(x, y) self.__maskImages[iid].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 iid, image in enumerate(self.__images): if axes.scale is not None or axes.position is not None: image.resetTransform() self.__maskImages[iid].resetTransform() if axes.scale is not None: self._imagescale(1, 1) for iid, image in enumerate(self.__images): if axes.position is not None: image.setPos(0, 0) self.__maskImages[iid].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, maskimg=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` :param maskarray: 2d color mask image array :type maskarray: :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() self.__maskImages[iid].hide() else: image.show() if self.__channellevels and \ self.levelMode() != 'mono': # print("SetImg: 1 - RGB with RGB levels " # "(3 mono grad separate images)") image.setImage( img[:, :, iid], levels=self.__channellevels[iid], autoLevels=False) elif self.__displaylevels[0] is not None \ and self.__displaylevels[1] \ is not None: # print("SetImg: 1b - RGB with MONO levels" # " (3 mono grad separate images)") image.setImage( img[:, :, iid], levels=self.__displaylevels, autoLevels=False) else: # print("SetImg: 1c - RGB with w/olevels " # "(3 mono grad separate images)") image.setImage( img[:, :, iid], # levels=[[0,255], [0, 255], [0, 255]], autoLevels=False) if maskimg is not None: # print(maskimg) if self.__overflowcolor: self.__maskImages[iid].show() # print(self.__overflowcolor) self.__maskImages[iid].setImage( np.array([ maskimg[:, :, iid].T * self.__overflowcolor[0], maskimg[:, :, iid].T * self.__overflowcolor[1], maskimg[:, :, iid].T * self.__overflowcolor[2] ]).T, levels=[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': # print("SetImg: 2a - RGB with RGB levels") 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: # print("SetImg: 2b - RGB with MONO levels ") self.__image.setImage( img, # lut=None, levels=self.__displaylevels, autoLevels=False) else: # print("SetImg: 2c - RGB with without levels") self.__image.setImage( img, # lut=None, # levels=[[0,255], [0, 255], [0, 255]], autoLevels=False) if maskimg is not None: # print(maskimg) if self.__overflowcolor: self.__maskImages[0].show() # print(self.__overflowcolor) smaskimg = np.sum(np.nan_to_num(maskimg), axis=2) smaskimg[smaskimg == 0] = np.nan self.__maskImages[0].setImage( np.array([ smaskimg.T * self.__overflowcolor[0], smaskimg.T * self.__overflowcolor[1], smaskimg.T * self.__overflowcolor[2] ]).T, levels=[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() # print("SetImg: 3") 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() # print("SetImg: 4") self.__image.setImage( img, autoLevels=False, autoDownsample=self.__autodownsample) else: self._hideimages() self.__image.show() # print("SetImg: 5") self.__image.setImage( img, autoLevels=False, levels=self.__displaylevels, autoDownsample=self.__autodownsample) if img is not None and len(img.shape) != 3: if maskimg is not None: # print(maskimg) if self.__overflowcolor: self.__maskImages[0].show() # print(self.__overflowcolor) self.__maskImages[0].setImage( np.array([ maskimg.T * self.__overflowcolor[0], maskimg.T * self.__overflowcolor[1], maskimg.T * self.__overflowcolor[2] ]).T, levels=[0, 255], autoLevels=False) 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() self.__maskImages[0].hide() self.__maskImages[1].hide() self.__maskImages[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