Source code for lavuelib.displayExtensions

# 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
from pyqtgraph import QtCore
import numpy as np
import math
import json
import time
import logging
# import warnings
from pyqtgraph.graphicsItems.ROI import ROI, LineROI, Handle
from pyqtgraph.graphicsItems.IsocurveItem import IsocurveItem

_VMAJOR, _VMINOR, _VPATCH = _pg.__version__.split(".")[:3] \
    if _pg.__version__ else ("0", "9", "0")

logger = logging.getLogger("lavue")


[docs]class HandleWithSignals(Handle): """ handle with signals """ #: (:class:`pyqtgraph.QtCore.pyqtSignal`) hover event emitted hovered = QtCore.pyqtSignal() def __init__(self, pos, center, parent): """ constructor :param pos: position of handle :type pos: [float, float] :param center: center of handle :type center: [float, float] :param parent: roi object :type parent: :class:`pyqtgraph.graphicsItems.ROI.ROI` """ pos = _pg.Point(pos) center = _pg.Point(center) if pos[0] != center[0] and pos[1] != center[1]: raise Exception( "Scale/rotate handles must have either the same x or y " "coordinate as their center point.") Handle.__init__(self, parent.handleSize, typ='sr', pen=parent.handlePen, parent=parent) self.setPos(pos * parent.state['size'])
[docs] def hoverEvent(self, ev): """ hover event :param ev: close event :type ev: :class:`pyqtgraph.QtCore.QEvent`: """ Handle.hoverEvent(self, ev) self.hovered.emit()
[docs]class SimpleLineROI(LineROI): """ simple line roi """ def __init__(self, pos1, pos2, width=0.00001, **args): """ constructor :param pos1: start position :type pos1: [float, float] :param pos2: end position :type pos2: [float, float] :param args: dictionary with ROI parameters :type args: :obj:`dict`<:obj:`str`, :obj:`any`> """ pos1 = _pg.Point(pos1) pos2 = _pg.Point(pos2) d = pos2 - pos1 ln = d.length() ang = _pg.Point(1, 0).angle(d) ROI.__init__(self, pos1, size=_pg.Point(ln, width), angle=ang, **args) h1pos = [0, 0.0] h1center = [1, 0.0] h2pos = [1, 0.0] h2center = [0, 0.0] vpos = [0.5, 1] vcenter = [0.5, 0] self.handle1 = HandleWithSignals(h1pos, h1center, self) self.handle2 = HandleWithSignals(h2pos, h2center, self) self.vhandle = HandleWithSignals(vcenter, vpos, self) self.addHandle( {'name': 'handle1', 'type': 'sr', 'center': h1center, 'pos': h1pos, 'item': self.handle1}) self.addHandle( {'name': 'handle2', 'type': 'sr', 'center': h2center, 'pos': h2pos, 'item': self.handle2}) self.addHandle( {'name': 'vhandle', 'type': 'sr', 'center': vcenter, 'pos': vpos, 'item': self.vhandle}) # self.handle1 = self.addScaleRotateHandle([0, 0.5], [1, 0.5]) # self.handle2 = self.addScaleRotateHandle([1, 0.5], [0, 0.5])
[docs] def getCoordinates(self): """ provides the roi coordinates :param trans: transposed flag :type trans: :obj:`bool` :returns: x1, y1, x2, y2 positions of the roi :rtype: [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] """ ang = self.state['angle'] pos1 = self.state['pos'] size = self.state['size'] ra = ang * np.pi / 180. pos2 = pos1 + _pg.Point( size.x() * math.cos(ra), size.x() * math.sin(ra)) return [pos1.x(), pos1.y(), pos2.x(), pos2.y(), size.y()]
[docs]class RegionItem(IsocurveItem): def __init__(self, points=None, pen='w', **args): """ constructor :param points: list of points :type points: :obj:`list` < (float, float) > :param pen: qt pen :type pen: :class:`pyqtgraph.QtGui.QPen` :param args: more params :type args: :obj:`dict` <:obj:`str`, `any`> """ IsocurveItem.__init__(self, points, 0, pen, None, **args) # if points and points[0] and points[0][0]: # self.setPos(*points[0][0])
[docs] def generatePath(self): """ generate QPainterPath """ if self.data is None: self.path = None return self.path = _pg.QtGui.QPainterPath() # nopos = True for line in self.data: if line and len(line) > 1: self.path.moveTo(*line[0]) # if nopos: # self.setPos(*line[0]) # nopos = False for p in line[1:]: self.path.lineTo(*p)
[docs]class DisplayExtension(QtCore.QObject): """ display extension for ImageDisplayWidget """ def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ QtCore.QObject.__init__(self) #: (:obj:`str`) extension name self.name = "none" #: (:class:`pyqtgraph.QtCore.QObject`) mainwidget self._mainwidget = parent #: (:obj:`bool`) enabled flag self._enabled = False #: (:obj:`float`) minimum refresh time in s self._refreshtime = 0.02
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """
[docs] def transpose(self): """ transposes subwidget """
[docs] def setRefreshTime(self, refreshtime): """ sets refresh time :param refreshtime: refresh time in seconds :type refreshtime: :obj:`float` """ self._refreshtime = refreshtime
[docs] def refreshTime(self): """ provides refresh time :returns: refresh time in seconds :rtype: :obj:`float` """ return self._refreshtime
[docs] def enabled(self): """ is extension enabled :returns: is extension enabled :rtype: :obj:`bool` """ return self._enabled
[docs] def coordinates(self): """ returns coordinates """ return None, None
[docs] def mouse_position(self, x, y): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float """
[docs] def mouse_doubleclick(self, x, y, locked): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float :param locked: double click lock :type locked: bool """
[docs] def mouse_click(self, x, y): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float """
[docs] def scalingLabel(self): """ provides scaling label :returns: scaling label :rtype: str """
[docs]class ROIExtension(DisplayExtension): #: (:class:`pyqtgraph.QtCore.pyqtSignal`) roi coordinate changed signal roiCoordsChanged = QtCore.pyqtSignal() def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "rois" #: (:class:`pyqtgraph.QtCore.QSignalMapper`) current roi mapper self.__currentroimapper = QtCore.QSignalMapper(self) #: (:class:`pyqtgraph.QtCore.QSignalMapper`) roi region mapper self.__roiregionmapper = QtCore.QSignalMapper(self) #: (:obj:`int`) current roi id self.__current = 0 #: (:obj:`list` < [int, int, int, int] > ) #: x1,y1,x2,y2 rois coordinates self.__coords = [[10, 10, 60, 60]] #: (:obj:`list` < (int, int, int) > ) list with roi colors self.__colors = [] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.TextItem`>) #: list of roi widgets self.__roitext = [] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.ROI`>) #: list of roi widgets self.__roi = [] self.__roi.append(ROI(0, _pg.Point(50, 50))) self.__roi[0].addScaleHandle([1, 1], [0, 0]) self.__roi[0].addScaleHandle([0, 0], [1, 1]) text = _pg.TextItem("1.", anchor=(1, 1)) text.setParentItem(self.__roi[0]) self.__roitext.append(text) self._mainwidget.viewbox().addItem(self.__roi[0]) self.__roi[0].hide() self.setColors() self.__roiregionmapper.mapped.connect(self.changeROIRegion) self.__currentroimapper.mapped.connect(self._emitROICoordsChanged) self._getROI().sigHoverEvent.connect( self.__currentroimapper.map) self._getROI().sigRegionChanged.connect( self.__roiregionmapper.map) self.__currentroimapper.setMapping(self._getROI(), 0) self.__roiregionmapper.setMapping(self._getROI(), 0)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.rois is not None: self.__showROIs(parameters.rois) self._enabled = parameters.rois
[docs] def scalingLabel(self): """ provides scaling label :returns: scaling label :rtype: str """ return "intensity"
def __addROI(self, coords=None): """ adds ROIs :param coords: roi coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if not coords or not isinstance(coords, list) or len(coords) != 4: pnt = 10 * len(self.__roi) sz = 50 coords = [pnt, pnt, pnt + sz, pnt + sz] spnt = _pg.Point(sz, sz) else: if not self._mainwidget.transformations()[0]: pnt = _pg.Point(coords[0], coords[1]) spnt = _pg.Point(coords[2] - coords[0], coords[3] - coords[1]) else: pnt = _pg.Point(coords[1], coords[0]) spnt = _pg.Point(coords[3] - coords[1], coords[2] - coords[0]) self.__roi.append(ROI(pnt, spnt)) self.__roi[-1].addScaleHandle([1, 1], [0, 0]) self.__roi[-1].addScaleHandle([0, 0], [1, 1]) text = _pg.TextItem("%s." % len(self.__roi), anchor=(1, 1)) text.setParentItem(self.__roi[-1]) self.__roitext.append(text) self._mainwidget.viewbox().addItem(self.__roi[-1]) self.__coords.append(coords) self.setColors() def __removeROI(self): """ removes the last roi """ roi = self.__roi.pop() roi.hide() roitext = self.__roitext.pop() roitext.hide() self._mainwidget.viewbox().removeItem(roi) self.__coords.pop() def _getROI(self, rid=-1): """ get the given or the last ROI :param rid: roi id :type rid: :obj:`int` """ if self.__roi and len(self.__roi) > rid: return self.__roi[rid] else: return None def __showROIs(self, status): """ shows or hides rois :param status: will be shown :type status: :obj:`bool` """ if status: for roi in self.__roi: roi.show() else: for roi in self.__roi: roi.hide() def __addROICoords(self, coords): """ adds ROI coorinates :param coords: roi coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if coords: for i, crd in enumerate(self.__roi): if i < len(coords): self.__coords[i] = coords[i] if not self._mainwidget.transformations()[0]: crd.setPos([coords[i][0], coords[i][1]]) crd.setSize( [coords[i][2] - coords[i][0], coords[i][3] - coords[i][1]]) else: crd.setPos([coords[i][1], coords[i][0]]) crd.setSize( [coords[i][3] - coords[i][1], coords[i][2] - coords[i][0]]) def __calcROIsum(self, rid): """calculates the current roi sum :param rid: roi id :type rid: :obj:`int` :returns: sum roi value, roi id :rtype: (float, int) """ if rid >= 0: image = self._mainwidget.rawData() if image is not None: if self._enabled: if rid >= 0: roicoords = self.__coords if not self._mainwidget.transformations()[0]: rcrds = list(roicoords[rid]) if self._mainwidget.rangeWindowEnabled(): tx, ty = self._mainwidget.descaledxy( rcrds[0], rcrds[1], useraxes=False) if tx is not None: tx2, ty2 = self._mainwidget.descaledxy( rcrds[2], rcrds[3], useraxes=False) rcrds = [tx, ty, tx2, ty2] else: rc = roicoords[rid] rcrds = [rc[1], rc[0], rc[3], rc[2]] if self._mainwidget.rangeWindowEnabled(): ty, tx = self._mainwidget.descaledxy( rcrds[1], rcrds[0], useraxes=False) if ty is not None: ty2, tx2 = self._mainwidget.descaledxy( rcrds[3], rcrds[2], useraxes=False) rcrds = [tx, ty, tx2, ty2] for i in [0, 2]: if rcrds[i] > image.shape[0]: rcrds[i] = image.shape[0] elif rcrds[i] < -i // 2: rcrds[i] = -i // 2 for i in [1, 3]: if rcrds[i] > image.shape[1]: rcrds[i] = image.shape[1] elif rcrds[i] < - (i - 1) // 2: rcrds[i] = - (i - 1) // 2 roival = np.nansum(image[ int(rcrds[0]):(int(rcrds[2]) + 1), int(rcrds[1]):(int(rcrds[3]) + 1) ]) else: roival = 0. else: roival = 0. return roival, rid else: return 0., rid return None, None
[docs] def calcROIsum(self): """calculates the current roi sum :returns: sum roi value, roi id :rtype: (float, int) """ if self._enabled and self._getROI() is not None: rid = self.__current return self.__calcROIsum(rid) return None, None
[docs] def calcROIsums(self): """ calculates all roi sums :returns: sum roi value, roi id :rtype: :obj:list < float > """ if self._mainwidget.rawData() is None: return None return [self.__calcROIsum(rid)[0] for rid in range(len(self.__coords))]
[docs] @QtCore.pyqtSlot(int) def changeROIRegion(self, _=None): """ changes the current roi region """ try: rid = self.__current roi = self._getROI(rid) if roi is not None: state = roi.state rcrds = [ state['pos'].x(), state['pos'].y(), state['pos'].x() + state['size'].x(), state['pos'].y() + state['size'].y()] if not self._mainwidget.transformations()[0]: ptx1 = int(math.floor(rcrds[0])) pty1 = int(math.floor(rcrds[1])) ptx2 = int(math.floor(rcrds[2])) pty2 = int(math.floor(rcrds[3])) else: pty1 = int(math.floor(rcrds[0])) ptx1 = int(math.floor(rcrds[1])) pty2 = int(math.floor(rcrds[2])) ptx2 = int(math.floor(rcrds[3])) crd = [ptx1, pty1, ptx2, pty2] if self.__coords[rid] != crd: self.__coords[rid] = crd self.roiCoordsChanged.emit() except Exception as e: logger.warning(str(e))
# print("Warning: %s" % str(e)) @QtCore.pyqtSlot(int) def _emitROICoordsChanged(self, rid): """ emits roiCoordsChanged signal :param rid: roi id :type rid: :obj:`int` """ oldrid = self.__current if rid != oldrid: self.__current = rid self.roiCoordsChanged.emit()
[docs] def updateROIs(self, rid, coords): """ update ROIs :param rid: roi id :type rid: :obj:`int` :param coords: roi coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ self.__addROICoords(coords) while rid > len(self.__roi): if coords and len(coords) >= len(self.__roi): self.__addROI(coords[len(self.__roi)]) else: self.__addROI() self._getROI().sigHoverEvent.connect(self.__currentroimapper.map) self._getROI().sigRegionChanged.connect(self.__roiregionmapper.map) self.__currentroimapper.setMapping( self._getROI(), len(self.__roi) - 1) self.__roiregionmapper.setMapping( self._getROI(), len(self.__roi) - 1) if rid <= 0: self.__current = -1 elif self.__current >= rid: self.__current = 0 while self._getROI(max(rid, 0)) is not None: self.__currentroimapper.removeMappings(self._getROI()) self.__roiregionmapper.removeMappings(self._getROI()) self.__removeROI() self.__showROIs(self._enabled)
[docs] def setColors(self, colors=None): """ sets colors :param colors: json list of roi colors :type colors: :obj:`str` :returns: change status :rtype: :obj:`bool` """ force = False if colors is not None: colors = json.loads(colors) if not isinstance(colors, list): return False for cl in colors: if not isinstance(cl, list): return False if len(cl) != 3: return False for clit in cl: if not isinstance(clit, int): return False else: colors = self.__colors force = True if self.__colors != colors or force: self.__colors = colors defpen = (255, 255, 255) for it, roi in enumerate(self.__roi): clr = tuple(colors[it % len(colors)]) if colors else defpen roi.setPen(clr) if hasattr(self.__roitext[it], "setColor"): self.__roitext[it].setColor(clr) else: self.__roitext[it].color = _pg.functions.mkColor(clr) self.__roitext[it].textItem.setDefaultTextColor( self.__roitext[it].color) return True
[docs] def roiCoords(self): """ provides rois coordinates :return: rois coordinates :rtype: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ return self.__coords
[docs] def isROIsEnabled(self): """ provides flag rois enabled :return: roi enabled flag :rtype: :obj:`bool` """ return self._enabled
[docs] def currentROI(self): """ provides current roi id :return: roi id :rtype: :obj:`int` """ return self.__current
[docs] def transpose(self): """ transposes ROIs """ for crd in self.__roi: pos = crd.pos() size = crd.size() crd.setPos([pos[1], pos[0]]) crd.setSize([size[1], size[0]])
[docs]class CutExtension(DisplayExtension): #: (:class:`pyqtgraph.QtCore.pyqtSignal`) cut coordinate changed signal cutCoordsChanged = QtCore.pyqtSignal() def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "cuts" #: (:class:`pyqtgraph.QtCore.QSignalMapper`) current cut mapper self.__currentcutmapper = QtCore.QSignalMapper(self) #: (:class:`pyqtgraph.QtCore.QSignalMapper`) cut region mapper self.__cutregionmapper = QtCore.QSignalMapper(self) #: (:obj:`int`) current cut id self.__current = 0 #: (:obj:`list` < [int, int, int, int] > ) #: x1,y1,x2,y2, width rois coordinates self.__coords = [[10, 10, 60, 10, 0.00001]] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.ROI`>) #: list of cut widgets self.__cut = [] self.__cut.append(SimpleLineROI([10, 10], [60, 10], pen='r')) self._mainwidget.viewbox().addItem(self.__cut[0]) self.__cut[0].hide() self.__cutregionmapper.mapped.connect(self.changeCutRegion) self.__currentcutmapper.mapped.connect(self._emitCutCoordsChanged) self._getCut().sigHoverEvent.connect( self.__currentcutmapper.map) self._getCut().sigRegionChanged.connect( self.__cutregionmapper.map) self._getCut().handle1.hovered.connect( self.__currentcutmapper.map) self._getCut().handle2.hovered.connect( self.__currentcutmapper.map) self._getCut().vhandle.hovered.connect( self.__currentcutmapper.map) self.__currentcutmapper.setMapping(self._getCut().handle1, 0) self.__currentcutmapper.setMapping(self._getCut().handle2, 0) self.__currentcutmapper.setMapping(self._getCut().vhandle, 0) self.__currentcutmapper.setMapping(self._getCut(), 0) self.__cutregionmapper.setMapping(self._getCut(), 0)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.cuts is not None: self.__showCuts(parameters.cuts) self._enabled = parameters.cuts
def __addCutCoords(self, coords): """ adds Cut coordinates :param coords: cut coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if coords: for i, crd in enumerate(self.__cut): if i < len(coords): self.__coords[i] = coords[i] if not self._mainwidget.transformations()[0]: crd.setPos([coords[i][0], coords[i][1]]) crd.setSize( [coords[i][2] - coords[i][0], coords[i][3] - coords[i][1]]) else: crd.setPos([coords[i][1], coords[i][0]]) crd.setSize( [coords[i][3] - coords[i][1], coords[i][2] - coords[i][0]]) def __addCut(self, coords=None): """ adds Cuts :param coords: cut coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if not coords or not isinstance(coords, list) or len(coords) != 5: pnt = 10 * (len(self.__cut) + 1) sz = 50 coords = [pnt, pnt, pnt + sz, pnt, 0.00001] if not self._mainwidget.transformations()[0]: self.__cut.append(SimpleLineROI( coords[:2], coords[2:4], width=coords[4], pen='r')) else: self.__cut.append(SimpleLineROI( [coords[1], coords[0]], [coords[3], coords[2]], width=coords[4], pen='r')) self._mainwidget.viewbox().addItem(self.__cut[-1]) self.__coords.append(coords) def __removeCut(self): """ removes the last cut """ cut = self.__cut.pop() cut.hide() self._mainwidget.viewbox().removeItem(cut) self.__coords.pop() def _getCut(self, cid=-1): """ get the given or the last Cut :param cid: roi id :type cid: :obj:`int` """ if self.__cut and len(self.__cut) > cid: return self.__cut[cid] else: return None def __showCuts(self, status): """ shows or hides cuts :param status: will be shown :type status: :obj:`bool` """ if status: for cut in self.__cut: cut.show() else: for cut in self.__cut: cut.hide()
[docs] def cutData(self, cid=None): """ provides the current cut data :param cid: cut id :type cid: :obj:`int` :returns: current cut data :rtype: :class:`numpy.ndarray` """ if cid is None: cid = self.__current if cid > -1 and len(self.__cut) > cid: cut = self._getCut(cid) if self._mainwidget.rawData() is not None: dt = cut.getArrayRegion( self._mainwidget.rawData(), self._mainwidget.image(), axes=(0, 1)) with np.warnings.catch_warnings(): np.warnings.filterwarnings( 'ignore', r'Mean of empty slice') while dt.ndim > 1: dt = np.nanmean(dt, axis=1) return dt return None
[docs] def cutCoords(self): """ provides cuts coordinates :return: cuts coordinates :rtype: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ return self.__coords
[docs] @QtCore.pyqtSlot(int) def changeCutRegion(self, _=None): """ changes the current roi region """ try: cid = self.__current crds = self._getCut(cid).getCoordinates() if not self._mainwidget.transformations()[0]: self.__coords[cid] = crds else: self.__coords[cid] = [ crds[1], crds[0], crds[3], crds[2], crds[4]] self.cutCoordsChanged.emit() except Exception as e: logger.warning(str(e))
# print("Warning: %s" % str(e)) @QtCore.pyqtSlot(int) def _emitCutCoordsChanged(self, cid): """ emits cutCoordsChanged signal :param cid: cut id :type cid: :obj:`int` """ oldcid = self.__current if cid != oldcid: self.__current = cid self.cutCoordsChanged.emit()
[docs] def updateCuts(self, cid, coords): """ update Cuts :param cid: cut id :type cid: :obj:`int` :param coords: cut coordinates :type coords: :obj:`list` < [float, float, float, float] > """ self.__addCutCoords(coords) while cid > len(self.__cut): if coords and len(coords) >= len(self.__cut): self.__addCut(coords[len(self.__cut)]) else: self.__addCut() self._getCut().sigHoverEvent.connect(self.__currentcutmapper.map) self._getCut().sigRegionChanged.connect(self.__cutregionmapper.map) self._getCut().handle1.hovered.connect(self.__currentcutmapper.map) self._getCut().handle2.hovered.connect(self.__currentcutmapper.map) self._getCut().vhandle.hovered.connect(self.__currentcutmapper.map) self.__currentcutmapper.setMapping( self._getCut(), len(self.__cut) - 1) self.__currentcutmapper.setMapping( self._getCut().handle1, len(self.__cut) - 1) self.__currentcutmapper.setMapping( self._getCut().handle2, len(self.__cut) - 1) self.__currentcutmapper.setMapping( self._getCut().vhandle, len(self.__cut) - 1) self.__cutregionmapper.setMapping( self._getCut(), len(self.__cut) - 1) if cid <= 0: self.__current = -1 elif self.__current >= cid: self.__current = 0 while max(cid, 0) < len(self.__cut): self.__currentcutmapper.removeMappings(self._getCut()) self.__currentcutmapper.removeMappings(self._getCut().handle1) self.__currentcutmapper.removeMappings(self._getCut().handle2) self.__cutregionmapper.removeMappings(self._getCut()) self.__removeCut()
[docs] def isCutsEnabled(self): """ provides flag cuts enabled :return: cut enabled flag :rtype: :obj:`bool` """ return self._enabled
[docs] def currentCut(self): """ provides current cut id :return: cut id :rtype: :obj:`int` """ return self.__current
[docs] def transpose(self): """ transposes Cuts """ for crd in self.__cut: pos = crd.pos() size = crd.size() angle = crd.angle() ra = angle * np.pi / 180. crd.setPos( [pos[1] + math.sin(ra) * size[0], pos[0] + math.cos(ra) * size[0]]) crd.setAngle(270-angle)
[docs]class MeshExtension(DisplayExtension): #: (:class:`pyqtgraph.QtCore.pyqtSignal`) roi coordinate changed signal roiCoordsChanged = QtCore.pyqtSignal() def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "mesh" #: (:class:`pyqtgraph.QtCore.QSignalMapper`) current roi mapper self.__currentroimapper = QtCore.QSignalMapper(self) #: (:class:`pyqtgraph.QtCore.QSignalMapper`) roi region mapper self.__roiregionmapper = QtCore.QSignalMapper(self) #: (:obj:`int`) current roi id self.__current = 0 #: (:obj:`list` < [int, int, int, int] > ) #: x1,y1,x2,y2 rois coordinates self.__coords = [[10, 10, 60, 60]] #: (:obj:`list` < (int, int, int) > ) list with roi colors self.__colors = [] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.TextItem`>) #: list of roi widgets self.__roitext = [] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.ROI`>) #: list of roi widgets self.__roi = [] self.__roi.append(ROI(0, _pg.Point(50, 50))) self.__roi[0].addScaleHandle([1, 1], [0, 0]) self.__roi[0].addScaleHandle([0, 0], [1, 1]) text = _pg.TextItem("1.", anchor=(1, 1)) text.setParentItem(self.__roi[0]) self.__roitext.append(text) self._mainwidget.viewbox().addItem(self.__roi[0]) self.__roi[0].hide() self.__roiregionmapper.mapped.connect(self.changeROIRegion) self.__currentroimapper.mapped.connect(self._emitROICoordsChanged) self._getROI().sigHoverEvent.connect( self.__currentroimapper.map) self._getROI().sigRegionChanged.connect( self.__roiregionmapper.map) self.__currentroimapper.setMapping(self._getROI(), 0) self.__roiregionmapper.setMapping(self._getROI(), 0)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.mesh is not None: self.__showROIs(parameters.mesh) self._enabled = parameters.mesh
[docs] def scalingLabel(self): """ provides scaling label :returns: scaling label :rtype: str """ return "intensity"
def _getROI(self, rid=-1): """ get the given or the last ROI :param rid: roi id :type rid: :obj:`int` """ if self.__roi and len(self.__roi) > rid: return self.__roi[rid] else: return None def __showROIs(self, status): """ shows or hides rois :param status: will be shown :type status: :obj:`bool` """ if status: for roi in self.__roi: roi.show() else: for roi in self.__roi: roi.hide() def __addROICoords(self, coords): """ adds ROI coorinates :param coords: roi coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if coords: for i, crd in enumerate(self.__roi): if i < len(coords): self.__coords[i] = coords[i] if not self._mainwidget.transformations()[0]: crd.setPos([coords[i][0], coords[i][1]]) crd.setSize( [coords[i][2] - coords[i][0], coords[i][3] - coords[i][1]]) else: crd.setPos([coords[i][1], coords[i][0]]) crd.setSize( [coords[i][3] - coords[i][1], coords[i][2] - coords[i][0]])
[docs] @QtCore.pyqtSlot(int) def changeROIRegion(self, _=None): """ changes the current roi region """ try: rid = self.__current roi = self._getROI(rid) if roi is not None: state = roi.state if not self._mainwidget.transformations()[0]: ptx = state['pos'].x() pty = state['pos'].y() szx = state['size'].x() szy = state['size'].y() else: pty = state['pos'].x() ptx = state['pos'].y() szy = state['size'].x() szx = state['size'].y() crd = [ptx, pty, ptx + szx, pty + szy] if self.__coords[rid] != crd: self.__coords[rid] = crd self.roiCoordsChanged.emit() except Exception as e: logger.warning(str(e))
# print("Warning: %s" % str(e)) @QtCore.pyqtSlot(int) def _emitROICoordsChanged(self, rid): """ emits roiCoordsChanged signal :param rid: roi id :type rid: :obj:`int` """ oldrid = self.__current if rid != oldrid: self.__current = rid self.roiCoordsChanged.emit()
[docs] def updateROIs(self, rid, coords): """ update ROIs :param rid: roi id :type rid: :obj:`int` :param coords: roi coordinates :type coords: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ self.__addROICoords(coords) while rid > len(self.__roi): if coords and len(coords) >= len(self.__roi): self.__addROI(coords[len(self.__roi)]) else: self.__addROI() self._getROI().sigHoverEvent.connect(self.__currentroimapper.map) self._getROI().sigRegionChanged.connect(self.__roiregionmapper.map) self.__currentroimapper.setMapping( self._getROI(), len(self.__roi) - 1) self.__roiregionmapper.setMapping( self._getROI(), len(self.__roi) - 1) if rid <= 0: self.__current = -1 elif self.__current >= rid: self.__current = 0 while self._getROI(max(rid, 0)) is not None: self.__currentroimapper.removeMappings(self._getROI()) self.__roiregionmapper.removeMappings(self._getROI()) self.__removeROI() self.__showROIs(self._enabled)
[docs] def roiCoords(self): """ provides rois coordinates :return: rois coordinates :rtype: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ return self.__coords
[docs] def isROIsEnabled(self): """ provides flag rois enabled :return: roi enabled flag :rtype: :obj:`bool` """ return self._enabled
[docs] def currentROI(self): """ provides current roi id :return: roi id :rtype: :obj:`int` """ return self.__current
[docs] def transpose(self): """ transposes ROIs """ for crd in self.__roi: pos = crd.pos() size = crd.size() crd.setPos([pos[1], pos[0]]) crd.setSize([size[1], size[0]])
[docs]class RegionsExtension(DisplayExtension): #: (:class:`pyqtgraph.QtCore.pyqtSignal`) region points changed signal regionPointsChanged = QtCore.pyqtSignal() def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "regions" #: (:obj:`int`) current roi id self.__current = 0 #: (:obj:`list` < [int, int, int, int] > ) #: x1,y1,x2,y2 regions coordinates self.__points = [[[(0, 0)]]] #: (:obj:`list` < (int, int, int) > ) list with region colors self.__colors = [] #: ( (:obj:`int`, :obj:`int', :obj:`int`)) default pen color self.__defpen = (0, 255, 127) #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.TextItem`>) #: list of region widgets self.__regiontext = [] #: (:obj:`list` <:class:`pyqtgraph.graphicsItems.ROI`>) #: list of region widgets self.__region = [] self.__region.append(RegionItem(self.__points[0], pen=_pg.mkPen('#00ff7f', width=2))) # text = _pg.TextItem("1.", anchor=(1, 1)) # text.setParentItem(self.__region[0]) # self.__regiontext.append(text) # clr = '#00ff7f' # if hasattr(self.__regiontext[0], "setColor"): # self.__regiontext[0].setColor(clr) # else: # self.__regiontext[0].color = _pg.functions.mkColor(clr) # self.__regiontext[0].textItem.setDefaultTextColor( # self.__regiontext[it].color) self._mainwidget.viewbox().addItem(self.__region[0]) self.__region[0].hide() self.setColors()
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.regions is not None: self.__showRegions(parameters.regions) self._enabled = parameters.regions
[docs] def scalingLabel(self): """ provides scaling label :returns: scaling label :rtype: str """ return "intensity"
def __addRegion(self, points=None, rid=0): """ adds Regions :param points: region coordinates :type points: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if not points or not isinstance(points, list): points = [[(0, 0)]] if self._mainwidget.transformations()[0]: points = [[(p[1], p[0]) for p in pt] for pt in points] clr = tuple(self.__colors[rid % len(self.__colors)]) \ if self.__colors else self.__defpen self.__region.append( RegionItem(points, pen=_pg.mkPen(clr, width=2))) # text = _pg.TextItem("%s." % len(self.__region), anchor=(1, 1)) # text.setParentItem(self.__region[-1]) # self.__regiontext.append(text) self._mainwidget.viewbox().addItem(self.__region[-1]) self.__points.append(points) # self.setColors() def __removeRegion(self): """ removes the last region """ region = self.__region.pop() region.hide() # regiontext = self.__regiontext.pop() # regiontext.hide() self._mainwidget.viewbox().removeItem(region) self.__points.pop() def _getRegion(self, rid=-1): """ get the given or the last region :param rid: region id :type rid: :obj:`int` """ if self.__region and len(self.__region) > rid: return self.__region[rid] else: return None def __showRegions(self, status): """ shows or hides regions :param status: will be shown :type status: :obj:`bool` """ if status: for rng in self.__region: rng.show() else: for rng in self.__region: rng.hide() def __addRegionPoints(self, points): """ adds region coorinates :param points: region coordinates :type points: :obj:`list` < [:obj:`float`, :obj:`float`, :obj:`float`, :obj:`float`] > """ if points: for i, crd in enumerate(self.__region): if i < len(points): self.__points[i] = points[i] if self._mainwidget.transformations()[0]: pnts = [[(p[1], p[0]) for p in pt] for pt in points[i]] else: pnts = points[i] crd.setData(pnts)
[docs] def updateRegions(self, points, rid=None): """ update Regions :param rid: rng id :type rid: :obj:`int` :param points: rng coordinates :type points: :obj:`list` < :obj:`list` < :obj:`list` < (:obj:`float`, :obj:`float`) > > > """ if rid is None: if points is None: points = [] rid = len(points) self.__addRegionPoints(points) while rid > len(self.__region): if points and len(points) >= len(self.__region): self.__addRegion(points[len(self.__region)], rid=len(self.__region)) else: self.__addRegion(rid=len(self.__region)) if rid <= 0: self.__current = -1 elif self.__current >= rid: self.__current = 0 while self._getRegion(max(rid, 0)) is not None: self.__removeRegion() self.__showRegions(self._enabled)
[docs] def setColors(self, colors=None): """ sets colors :param colors: json list of roi colors :type colors: :obj:`str` :returns: change status :rtype: :obj:`bool` """ force = False if colors is not None: colors = json.loads(colors) if not isinstance(colors, list): return False for cl in colors: if not isinstance(cl, list): return False if len(cl) != 3: return False for clit in cl: if not isinstance(clit, int): return False else: colors = self.__colors force = True if self.__colors != colors or force: self.__colors = colors for it, rg in enumerate(self.__region): clr = tuple(colors[it % len(colors)]) if colors \ else self.__defpen rg.setPen(_pg.mkPen(clr, width=2)) # if hasattr(self.__regiontext[it], "setColor"): # self.__regiontext[it].setColor(clr) # else: # self.__regiontext[it].color = _pg.functions.mkColor(clr) # self.__regiontext[it].textItem.setDefaultTextColor( # self.__regiontext[it].color) return True
[docs] def regionPoints(self): """ provides region coordinates :return: region coordinates :rtype: :obj:`list` < :obj:`list` < :obj:`list` < (:obj:`float`, :obj:`float`) > > > """ return self.__points
[docs] def isRegionEnabled(self): """ provides flag regions enabled :return: region enabled flag :rtype: :obj:`bool` """ return self._enabled
[docs] def currentRegion(self): """ provides current region id :return: region id :rtype: :obj:`int` """ return self.__current
[docs] def transpose(self): """ transposes Regions """ for i, crd in enumerate(self.__region): if i < len(self.__points): if self._mainwidget.transformations()[0]: pnts = [[(p[1], p[0]) for p in pt] for pt in self.__points[i]] else: pnts = self.__points[i] crd.setData(pnts)
[docs]class LockerExtension(DisplayExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "locker" #: (:obj:`bool`) crooshair locked flag self.__crosshairlocked = False #: ([:obj:`float`, :obj:`float`]) position mark coordinates self.__lockercoordinates = None #: (:obj:`float`) last time in s self.__lasttime = 0. #: (:class:`pyqtgraph.InfiniteLine`) #: vertical locker line of the mouse position self.__lockerVLine = _pg.InfiniteLine( angle=90, movable=False, pen=(255, 0, 0)) #: (:class:`pyqtgraph.InfiniteLine`) #: horizontal locker line of the mouse position self.__lockerHLine = _pg.InfiniteLine( angle=0, movable=False, pen=(255, 0, 0)) self._mainwidget.viewbox().addItem( self.__lockerVLine, ignoreBounds=True) self._mainwidget.viewbox().addItem( self.__lockerHLine, ignoreBounds=True)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.crosshairlocker is not None: self.__showLockerLines(parameters.crosshairlocker) self._enabled = parameters.crosshairlocker
[docs] def coordinates(self): """ returns coordinates """ xfdata = None yfdata = None if self.__crosshairlocked: xfdata = math.floor(self.__lockercoordinates[0]) yfdata = math.floor(self.__lockercoordinates[1]) return xfdata, yfdata
def __showLockerLines(self, status): """ shows or hides HV locker mouse lines :param status: will be shown :type status: :obj:`bool` """ if status: self.__lockerVLine.show() self.__lockerHLine.show() else: self.__lockerVLine.hide() self.__lockerHLine.hide()
[docs] def mouse_position(self, x, y): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float """ if not self.__crosshairlocked: now = time.time() if now - self.__lasttime > self._refreshtime: self.__lasttime = now pos0, pos1, scale0, scale1 = self._mainwidget.scale() fx = math.floor(x) fy = math.floor(y) if pos0 is not None: if not self._mainwidget.transformations()[0]: self.__lockerVLine.setPos((fx + .5) * scale0 + pos0) self.__lockerHLine.setPos((fy + .5) * scale1 + pos1) else: self.__lockerVLine.setPos((fy + .5) * scale1 + pos1) self.__lockerHLine.setPos((fx + .5) * scale0 + pos0) else: if not self._mainwidget.transformations()[0]: self.__lockerVLine.setPos(fx + .5) self.__lockerHLine.setPos(fy + .5) else: self.__lockerVLine.setPos(fy + .5) self.__lockerHLine.setPos(fx + .5)
[docs] def mouse_doubleclick(self, x, y, locked): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float :param locked: double click lock :type locked: bool """ self.updateLocker(x, y)
[docs] @QtCore.pyqtSlot(float, float) def updateLocker(self, xdata, ydata): """ updates the locker position :param xdata: x pixel position :type xdata: :obj:`float` :param ydata: y-pixel position :type ydata: :obj:`float` """ self.__crosshairlocked = not self.__crosshairlocked if not self.__crosshairlocked: if not self._mainwidget.transformations()[0]: self.__lockerVLine.setPos(xdata + 0.5) self.__lockerHLine.setPos(ydata + 0.5) else: self.__lockerVLine.setPos(ydata + 0.5) self.__lockerHLine.setPos(xdata + 0.5) else: self.__lockercoordinates = [xdata, ydata]
[docs] def transpose(self): """ transposes locker lines """ v = self.__lockerHLine.getPos()[1] h = self.__lockerVLine.getPos()[0] self.__lockerVLine.setPos(v) self.__lockerHLine.setPos(h)
[docs]class CenterExtension(DisplayExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "center" #: ([:obj:`float`, :obj:`float`]) center coordinates self.__centercoordinates = None #: (:obj:`float`) last time in s self.__lasttime = 0. #: (:class:`pyqtgraph.InfiniteLine`) #: vertical center line of the mouse position self.__centerVLine = _pg.InfiniteLine( angle=90, movable=False, pen=(0, 255, 0)) #: (:class:`pyqtgraph.InfiniteLine`) #: horizontal center line of the mouse position self.__centerHLine = _pg.InfiniteLine( angle=0, movable=False, pen=(0, 255, 0)) self._mainwidget.viewbox().addItem( self.__centerVLine, ignoreBounds=True) self._mainwidget.viewbox().addItem( self.__centerHLine, ignoreBounds=True)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.centerlines is not None: self.__showCenterLines(parameters.centerlines) self._enabled = parameters.centerlines
def __showCenterLines(self, status): """ shows or hides HV center mouse lines :param status: will be shown :type status: :obj:`bool` """ if status: self.__centerVLine.show() self.__centerHLine.show() else: self.__centerVLine.hide() self.__centerHLine.hide()
[docs] def mouse_position(self, x, y): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float """ if not self.__centercoordinates: now = time.time() if now - self.__lasttime > self._refreshtime: self.__lasttime = now if not self._mainwidget.transformations()[0]: self.__centerVLine.setPos(x) self.__centerHLine.setPos(y) else: self.__centerVLine.setPos(y) self.__centerHLine.setPos(x)
[docs] def mouse_doubleclick(self, x, y, locked): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float :param locked: double click lock :type locked: bool """ self.updateCenter(x, y)
[docs] @QtCore.pyqtSlot(float, float) def updateCenter(self, xdata, ydata): """ updates the image center :param xdata: x pixel position :type xdata: :obj:`float` :param ydata: y-pixel position :type ydata: :obj:`float` """ self.__centercoordinates = [xdata, ydata] pos0, pos1, scale0, scale1 = self._mainwidget.scale(useraxes=False) if pos0 is not None: if not self._mainwidget.transformations()[0]: self.__centerVLine.setPos((xdata) * scale0 + pos0) self.__centerHLine.setPos((ydata) * scale1 + pos1) else: self.__centerVLine.setPos((ydata) * scale1 + pos1) self.__centerHLine.setPos((xdata) * scale0 + pos0) else: if not self._mainwidget.transformations()[0]: self.__centerVLine.setPos(xdata) self.__centerHLine.setPos(ydata) else: self.__centerVLine.setPos(ydata) self.__centerHLine.setPos(xdata)
[docs] def transpose(self): """ transposes Center lines """ v = self.__centerHLine.getPos()[1] h = self.__centerVLine.getPos()[0] self.__centerVLine.setPos(v) self.__centerHLine.setPos(h)
[docs]class VHBoundsExtension(DisplayExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "vhbounds" #: ([:obj:`float`, :obj:`float`]) vertical bound coordinates self.__vbounds = None #: ([:obj:`float`, :obj:`float`]) horizontal bound coordinates self.__hbounds = None #: (:class:`pyqtgraph.InfiniteLine`) #: first vertical center line of the mouse position self.__centerVLine1 = _pg.InfiniteLine( angle=90, movable=False, pen=_pg.mkPen('r', width=2)) #: (:class:`pyqtgraph.InfiniteLine`) #: second vertical center line of the mouse position self.__centerVLine2 = _pg.InfiniteLine( angle=90, movable=False, pen=_pg.mkPen('r', width=2)) #: (:class:`pyqtgraph.InfiniteLine`) #: first horizontal center line of the mouse position self.__centerHLine1 = _pg.InfiniteLine( angle=0, movable=False, pen=_pg.mkPen('r', width=2)) #: (:class:`pyqtgraph.InfiniteLine`) #: second horizontal center line of the mouse position self.__centerHLine2 = _pg.InfiniteLine( angle=0, movable=False, pen=_pg.mkPen('r', width=2)) self._mainwidget.viewbox().addItem( self.__centerVLine1, ignoreBounds=True) self._mainwidget.viewbox().addItem( self.__centerVLine2, ignoreBounds=True) self._mainwidget.viewbox().addItem( self.__centerHLine1, ignoreBounds=True) self._mainwidget.viewbox().addItem( self.__centerHLine2, ignoreBounds=True)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.vhbounds is not None: self.__showBounds(parameters.vhbounds) self._enabled = parameters.vhbounds
def __showBounds(self, status): """ shows or hides HV center mouse lines :param status: will be shown :type status: :obj:`bool` """ if status: self.__centerVLine1.show() self.__centerHLine1.show() self.__centerVLine2.show() self.__centerHLine2.show() else: self.__centerVLine1.hide() self.__centerHLine1.hide() self.__centerVLine2.hide() self.__centerHLine2.hide()
[docs] @QtCore.pyqtSlot(float, float) def updateVBounds(self, xdata1, xdata2): """ updates the vertical bounds :param xdata1: first x-pixel position :type xdata1: :obj:`float` :param xdata2: second x-pixel position :type xdata2: :obj:`float` """ self.__vbounds = [xdata1, xdata2] if xdata1 is None: self.__centerVLine1.hide() else: self.__centerVLine1.setPos(xdata1) self.__centerVLine1.show() if xdata2 is None: self.__centerVLine2.hide() else: self.__centerVLine2.setPos(xdata2) self.__centerVLine2.show()
[docs] @QtCore.pyqtSlot(float, float) def updateHBounds(self, ydata1, ydata2): """ updates the horizontal bounds :param ydata1: first y-pixel position :type ydata1: :obj:`float` :param ydata2: second y-pixel position :type ydata2: :obj:`float` """ self.__hbounds = [ydata1, ydata2] if ydata1 is None: self.__centerHLine1.hide() else: self.__centerHLine1.setPos(ydata1) self.__centerHLine1.show() if ydata2 is None: self.__centerHLine2.hide() else: self.__centerHLine2.setPos(ydata2) self.__centerHLine2.show()
[docs]class BaseMarkExtension(DisplayExtension): """ base mark extension """ def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "basemark" #: ([:obj:`float`, :obj:`float`]) position mark coordinates self._markcoordinates = None #: (:obj:`float`) last time in s self._lasttime = 0. #: (:class:`pyqtgraph.InfiniteLine`) #: vertical mark line of the mouse position self._markVLine = _pg.InfiniteLine( angle=90, movable=False, pen=(255, 255, 255)) #: (:class:`pyqtgraph.InfiniteLine`) #: horizontal mark line of the mouse position self._markHLine = _pg.InfiniteLine( angle=0, movable=False, pen=(255, 255, 255)) self._mainwidget.viewbox().addItem(self._markVLine, ignoreBounds=True) self._mainwidget.viewbox().addItem(self._markHLine, ignoreBounds=True) def _showMarkLines(self, status): """ shows or hides HV mark mouse lines :param status: will be shown :type status: :obj:`bool` """ if status: self._markVLine.show() self._markHLine.show() else: self._markVLine.hide() self._markHLine.hide()
[docs] @QtCore.pyqtSlot(float, float) def updatePositionMark(self, xdata, ydata, scaled=False): """ updates the position mark :param xdata: x pixel position :type xdata: :obj:`float` :param ydata: y-pixel position :type ydata: :obj:`float` :param scaled: scaled flag :type scaled: :obj:`bool` """ self._markcoordinates = [xdata, ydata] pos0, pos1, scale0, scale1 = self._mainwidget.scale() if pos0 is not None and not scaled: if not self._mainwidget.transformations()[0]: self._markVLine.setPos((xdata) * scale0 + pos0) self._markHLine.setPos((ydata) * scale1 + pos1) else: self._markVLine.setPos((ydata) * scale1 + pos1) self._markHLine.setPos((xdata) * scale0 + pos0) else: if not self._mainwidget.transformations()[0]: self._markVLine.setPos(xdata) self._markHLine.setPos(ydata) else: self._markVLine.setPos(ydata) self._markHLine.setPos(xdata)
[docs] def transpose(self): """ transposes Mark Position lines """ v = self._markHLine.getPos()[1] h = self._markVLine.getPos()[0] self._markVLine.setPos(v) self._markHLine.setPos(h)
[docs]class MarkExtension(BaseMarkExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ BaseMarkExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "mark" self._markVLine.setPen((0, 0, 255)) self._markHLine.setPen((0, 0, 255))
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.marklines is not None: self._showMarkLines(parameters.marklines) self._enabled = parameters.marklines
[docs] def mouse_position(self, x, y): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float """ if not self._markcoordinates: now = time.time() if now - self._lasttime > self._refreshtime: self._lasttime = now pos0, pos1, scale0, scale1 = self._mainwidget.scale() if pos0 is not None: if not self._mainwidget.transformations()[0]: self._markVLine.setPos((x) * scale0 + pos0) self._markHLine.setPos((y) * scale1 + pos1) else: self._markVLine.setPos((y) * scale1 + pos1) self._markHLine.setPos((x) * scale0 + pos0) else: if not self._mainwidget.transformations()[0]: self._markVLine.setPos(x) self._markHLine.setPos(y) else: self._markVLine.setPos(y) self._markHLine.setPos(x)
[docs] def mouse_doubleclick(self, x, y, locked): """ sets vLine and hLine positions :param x: x coordinate :type x: float :param y: y coordinate :type y: float :param locked: double click lock :type locked: bool """ if not locked: self.updatePositionMark(x, y)
[docs]class TrackingExtension(BaseMarkExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ BaseMarkExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "tracking" self._markVLine.setPen((0, 255, 255)) self._markHLine.setPen((0, 255, 255))
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.trackinglines is not None: self._showMarkLines(parameters.trackinglines) self._enabled = parameters.trackinglines
[docs]class MaximaExtension(DisplayExtension): def __init__(self, parent=None): """ constructor :param parent: parent object :type parent: :class:`pyqtgraph.QtCore.QObject` """ DisplayExtension.__init__(self, parent) #: (:obj:`str`) tool name self.name = "maxima" #: (:obj:`list` < > ) maxima parameters self.__positions = [] self.__maxplot = _pg.ScatterPlotItem( size=30, symbol='+', pen=_pg.mkPen((0, 0, 0))) self._mainwidget.viewbox().addItem(self.__maxplot)
[docs] def show(self, parameters): """ set subwidget properties :param parameters: tool parameters :type parameters: :class:`lavuelib.toolWidget.ToolParameters` """ if parameters.maxima is not None: self.__showMaxima(parameters.maxima) self._enabled = parameters.maxima
def __showMaxima(self, status): """ shows or hides maxima :param status: will be shown :type status: :obj:`bool` """ if status: self.__maxplot.show() else: self.__maxplot.hide()
[docs] def setMaximaPos(self, positionlist, offset=None): """ sets maxima postions :param positionlist: [(x1, y1), ... , (xn, yn)] :type positionlist: :obj:`list` < (float, float) > :param offset: offset of position :type offset: [ :obj:`float`, :obj:`float`] """ self.__positions = positionlist if offset is None: offset = [0.5, 0.5] spots = [{'pos': [i + offset[0], j + offset[1]], 'data': 1, 'brush': _pg.mkBrush((255, 0, 255))} for i, j in self.__positions] if spots: spots[-1]['brush'] = _pg.mkBrush((255, 255, 0)) self.__maxplot.clear() self.__maxplot.addPoints(spots)
[docs] def transpose(self): """ transposes maxima """ positionlist = [(pos[1], pos[0]) for pos in self.__positions] self.setMaximaPos(positionlist)