Source code for lavuelib.toolWidget
# 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:
# Jan Kotanski <jan.kotanski@desy.de>
# Christoph Rosemann <christoph.rosemann@desy.de>
#
""" image widget """
from .qtuic import uic
import os
import re
import math
import sys
import time
import numpy as np
import scipy.optimize
import scipy.interpolate
import pyqtgraph as _pg
import logging
import random
import json
from pyqtgraph import QtCore, QtGui, functions
from enum import Enum
try:
from pyqtgraph import QtWidgets
except Exception:
from pyqtgraph import QtGui as QtWidgets
from . import geometryDialog
from . import rangeDialog
from . import diffRangeDialog
from . import takeMotorsDialog
from . import intervalsDialog
from . import motorWatchThread
from . import edDictDialog
from . import edListDialog
from . import commandThread
from .sardanaUtils import debugmethod, numpyEncoder
try:
try:
import tango
except ImportError:
import PyTango as tango
#: (:obj:`bool`) tango imported
TANGO = True
except ImportError:
#: (:obj:`bool`) tango imported
TANGO = False
try:
import pyFAI
#: (:obj:`bool`) pyFAI imported
PYFAI = True
except ImportError:
#: (:obj:`bool`) pyFAI imported
PYFAI = False
if sys.version_info > (3,):
long = int
_intensityformclass, _intensitybaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "IntensityToolWidget.ui"))
_roiformclass, _roibaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "ROIToolWidget.ui"))
_cutformclass, _cutbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "LineCutToolWidget.ui"))
_angleqformclass, _angleqbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "AngleQToolWidget.ui"))
_diffractogramformclass, _diffractogrambaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "DiffractogramToolWidget.ui"))
_maximaformclass, _maximabaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "MaximaToolWidget.ui"))
_motorsformclass, _motorsbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "MotorsToolWidget.ui"))
_meshformclass, _meshbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "MeshToolWidget.ui"))
_onedformclass, _onedbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "OneDToolWidget.ui"))
_projectionformclass, _projectionbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "ProjectionToolWidget.ui"))
_parametersformclass, _parametersbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "ParametersToolWidget.ui"))
_qroiprojformclass, _qroiprojbaseclass = uic.loadUiType(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ui", "QROIProjToolWidget.ui"))
__all__ = [
'IntensityToolWidget',
'ROIToolWidget',
'LineCutToolWidget',
'AngleQToolWidget',
'MotorsToolWidget',
'MeshToolWidget',
'OneDToolWidget',
'ProjectionToolWidget',
'MaximaToolWidget',
'ParametersToolWidget',
'DiffractogramToolWidget',
'QROIProjToolWidget',
'twproperties',
]
logger = logging.getLogger("lavue")
class Converters(object):
""" set of converters
"""
@classmethod
def toBool(cls, value):
""" converts to bool
:param value: variable to convert
:type value: any
:returns: result in bool type
:rtype: :obj:`bool`
"""
if type(value).__name__ == 'str' or type(value).__name__ == 'unicode':
lvalue = value.strip().lower()
if lvalue == 'false' or lvalue == '0':
return False
else:
return True
elif value:
return True
return False
class ToolParameters(object):
""" tool parameters
"""
def __init__(self):
""" constructor
"""
#: (:obj:`bool`) lines enabled
# self.lines = False
#: (:obj:`bool`) rois enabled
self.rois = False
#: (:obj:`bool`) cuts enabled
self.cuts = False
#: (:obj:`bool`) mesh enabled
self.mesh = False
#: (:obj:`bool`) axes scale enabled
self.scale = False
#: (:obj:`bool`) tool axes scale enabled
self.toolscale = False
#: (:obj:`bool`) bottom 1d plot enabled
self.bottomplot = False
#: (:obj:`bool`) right 1d plot enabled
self.rightplot = False
#: (:obj:`bool`) cross hair locker enabled
self.crosshairlocker = False
#: (:obj:`bool`) center lines enabled
self.centerlines = False
#: (:obj:`bool`) mark position lines enabled
self.marklines = False
#: (:obj:`bool`) tracking position lines enabled
self.trackinglines = False
#: (:obj:`str`) infolineedit text
self.infolineedit = None
#: (:obj:`str`) infolabel text
self.infolabel = None
#: (:obj:`str`) infolabel text
self.infotips = None
#: (:obj:`str`) show maxima
self.maxima = False
#: (:obj:`bool`) vertical and horizontal bounds
self.vhbounds = False
#: (:obj:`bool`) angle range enabled
self.regions = False
class ToolBaseWidget(QtWidgets.QWidget):
""" tool widget
"""
#: (:obj:`str`) tool name
name = "None"
#: (:obj:`str`) tool name alias
alias = "none"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
QtWidgets.QWidget.__init__(self, parent)
#: (:class:`pyqtgraph.QtCore.QObject`) mainwidget
self._mainwidget = parent
#: (:class:`Ui_ToolBaseWidget')
#: ui_toolwidget object from qtdesigner
self._ui = None
#: (:class:`ToolParameters`) tool parameters
self.parameters = ToolParameters()
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = []
def activate(self):
""" activates tool widget
"""
def deactivate(self):
""" deactivates tool widget
"""
def afterplot(self):
""" command after plot
"""
def beforeplot(self, array, rawarray):
""" command before plot
:param array: 2d image array
:type array: :class:`numpy.ndarray`
:param rawarray: 2d raw image array
:type rawarray: :class:`numpy.ndarray`
:return: 2d image array and raw image
:rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`)
"""
def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
return ""
[docs]class IntensityToolWidget(ToolBaseWidget):
""" intensity tool widget
"""
#: (:obj:`str`) tool name
name = "Intensity"
#: (:obj:`str`) tool name alias
alias = "intensity"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`Ui_IntensityToolWidget')
#: ui_toolwidget object from qtdesigner
self.__ui = _intensityformclass()
self.__ui.setupUi(self)
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self.parameters.scale = True
self.parameters.crosshairlocker = True
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
self.__ui.crosshairCheckBox.setChecked(self.__settings.crosshairlocker)
self._updateCrossHairLocker(self.__settings.crosshairlocker)
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.axesPushButton.clicked, self._mainwidget.setTicks],
[self.__ui.crosshairCheckBox.stateChanged,
self._mainwidget.emitTCC],
[self.__ui.crosshairCheckBox.stateChanged,
self._updateCrossHairLocker],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
@QtCore.pyqtSlot(int)
def _updateCrossHairLocker(self, status):
self.parameters.crosshairlocker = bool(status)
self._mainwidget.updateinfowidgets(self.parameters)
@QtCore.pyqtSlot()
def _message(self):
""" provides intensity message
"""
x, y, intensity = self._mainwidget.currentIntensity()[:3]
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
ilabel = self._mainwidget.scalingLabel()
txdata, tydata = self._mainwidget.scaledxy(x, y)
xunits, yunits = self._mainwidget.axesunits()
if isinstance(intensity, np.ndarray) and \
intensity.size <= 3:
itn = [0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity]
if len(itn) >= 3:
if txdata is not None:
message = "x = %f%s, y = %f%s, " \
"%s = (%.2f, %.2f, %.2f)" % (
txdata,
(" %s" % xunits) if xunits else "",
tydata,
(" %s" % yunits) if yunits else "",
ilabel,
itn[0], itn[1], itn[2])
else:
message = "x = %i%s, y = %i%s, " \
"%s = (%.2f, %.2f, %.2f)" % (
x,
(" %s" % xunits) if xunits else "",
y,
(" %s" % yunits) if yunits else "",
ilabel,
itn[0], itn[1], itn[2])
self._mainwidget.setDisplayedText(message)
else:
if txdata is not None:
message = "x = %f%s, y = %f%s, %s = %.2f" % (
txdata,
(" %s" % xunits) if xunits else "",
tydata,
(" %s" % yunits) if yunits else "",
ilabel,
intensity
)
else:
message = "x = %i%s, y = %i%s, %s = %.2f" % (
x,
(" %s" % xunits) if xunits else "",
y,
(" %s" % yunits) if yunits else "",
ilabel,
intensity)
self._mainwidget.setDisplayedText(message)
[docs] def afterplot(self):
""" command after plot
"""
if self.__settings.sendresults:
self.__sendresults()
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "crosshair_locker" in cnf.keys():
crosshairlocker = cnf["crosshair_locker"]
self.__ui.crosshairCheckBox.setChecked(crosshairlocker)
pars = ["position", "scale",
"xtext", "ytext", "xunits", "yunits"]
if any(par in cnf.keys() for par in pars):
self._mainwidget.updateTicks(cnf)
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["crosshair_locker"] = self.__ui.crosshairCheckBox.isChecked()
xpos, ypos, xsc, ysc = self._mainwidget.scale()
cnf["xunits"], cnf["yunits"] = self._mainwidget.axesunits()
cnf["xtext"], cnf["ytext"] = self._mainwidget.axestext()
cnf["position"] = [xpos, ypos]
cnf["scale"] = [xsc, ysc]
return json.dumps(cnf, cls=numpyEncoder)
def __sendresults(self):
""" send results to LavueController
"""
x, y, intensity = self._mainwidget.currentIntensity()[:3]
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
scaling = self._mainwidget.scalingLabel()
sx, sy = self._mainwidget.scaledxy(x, y)
xunits, yunits = self._mainwidget.axesunits()
results = {"tool": self.alias}
if isinstance(intensity, np.ndarray):
intensity = [0 if (isinstance(it, float) and np.isnan(it))
else float(it) for it in intensity]
results["intensity"] = intensity
else:
results["intensity"] = float(intensity)
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["pixel"] = [float(x), float(y)]
results["scaled_coordiantes"] = [float(sx), float(sy)]
results["coordiantes_units"] = [xunits, yunits]
results["intensity_scaling"] = scaling
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
[docs]class MotorsToolWidget(ToolBaseWidget):
""" motors tool widget
"""
#: (:obj:`str`) tool name
name = "MoveMotors"
#: (:obj:`str`) tool name alias
alias = "movemotors"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ("TANGO",)
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`str`) x-motor name
self.__xmotorname = ""
#: (:obj:`str`) y-motor name
self.__ymotorname = ""
#: (:obj:`str`) x final position
self.__xfinal = None
#: (:obj:`str`) y final position
self.__yfinal = None
#: (:obj:`str`) state of x-motor
self.__statex = None
#: (:obj:`str`) state of y-motor
self.__statey = None
#: (:class:`tango.DeviceProxy`) x-motor device
self.__xmotordevice = None
#: (:class:`tango.DeviceProxy`) y-motor device
self.__ymotordevice = None
#: (:class:`lavuelib.motorWatchThread.motorWatchThread`) motor watcher
self.__motorWatcher = None
#: (:obj:`bool`) is moving
self.__moving = False
#: (:obj:`bool`) is tracking
self.__tracking = False
#: (:class:`Ui_MotorsToolWidget')
#: ui_toolwidget object from qtdesigner
self.__ui = _motorsformclass()
self.__ui.setupUi(self)
self.__ui.xcurLineEdit.hide()
self.__ui.ycurLineEdit.hide()
#: (:obj:`bool`) position lines enabled
self.parameters.scale = True
self.parameters.marklines = True
self.parameters.trackinglines = False
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.axesPushButton.clicked, self._mainwidget.setTicks],
[self.__ui.takePushButton.clicked, self._setMotors],
[self.__ui.movePushButton.clicked, self._moveStopMotors],
[self.__ui.trackPushButton.clicked, self._trackStopMotors],
[self._mainwidget.mouseImageDoubleClicked, self._updateFinal],
[self._mainwidget.mouseImagePositionChanged, self._message],
[self.__ui.xLineEdit.textEdited, self._getFinal],
[self.__ui.xLineEdit.textChanged, self._mainwidget.emitTCC],
[self.__ui.yLineEdit.textEdited, self._getFinal],
[self.__ui.yLineEdit.textChanged, self._mainwidget.emitTCC],
]
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
pars = ["position", "scale",
"xtext", "ytext", "xunits", "yunits"]
if any(par in cnf.keys() for par in pars):
self._mainwidget.updateTicks(cnf)
if "motors" in cnf.keys():
try:
motorname = cnf["motors"][0]
motordevice = tango.DeviceProxy(motorname)
for attr in ["state", "position"]:
if not hasattr(motordevice, attr):
raise Exception("Missing %s" % attr)
self.__xmotorname = motorname
self.__xmotordevice = motordevice
except Exception as e:
logger.warning(str(e))
try:
motorname = cnf["motors"][1]
motordevice = tango.DeviceProxy(motorname)
for attr in ["state", "position"]:
if not hasattr(motordevice, attr):
raise Exception("Missing %s" % attr)
self.__ymotorname = motorname
self.__ymotordevice = motordevice
except Exception as e:
logger.warning(str(e))
if "x_position" in cnf.keys():
try:
self.__ui.xLineEdit.setText(str(cnf["x_position"]))
except Exception:
pass
if "y_position" in cnf.keys():
try:
self.__ui.yLineEdit.setText(str(cnf["y_position"]))
except Exception:
pass
if "track" in cnf.keys():
if cnf["track"]:
if str(self.__ui.trackePushButton.text()) == "Track":
self._trackStopMotors()
if "untrack" in cnf.keys():
if cnf["untrack"]:
if str(self.__ui.trackePushButton.text()) == "Untrack":
self._trackStopMotors()
if "move" in cnf.keys():
if cnf["move"]:
if str(self.__ui.movePushButton.text()) == "Move":
self._moveStopMotors()
if "stop" in cnf.keys():
if cnf["stop"]:
if str(self.__ui.movePushButton.text()) == "Stop":
self._moveStopMotors()
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
xpos, ypos, xsc, ysc = self._mainwidget.scale()
cnf["xunits"], cnf["yunits"] = self._mainwidget.axesunits()
cnf["xtext"], cnf["ytext"] = self._mainwidget.axestext()
cnf["position"] = [xpos, ypos]
cnf["scale"] = [xsc, ysc]
try:
cnf["x_position"] = float(self.__ui.xLineEdit.text())
except Exception:
cnf["x_position"] = self.__ui.xLineEdit.text()
try:
cnf["y_position"] = float(self.__ui.yLineEdit.text())
except Exception:
cnf["y_position"] = self.__ui.yLineEdit.text()
cnf["motors"] = [self.__xmotorname, self.__ymotorname]
if str(self.__ui.movePushButton.text()) == "Move":
cnf["motor_state"] = "ON"
else:
cnf["motor_state"] = "MOVING"
if self.__tracking:
cnf["tracking"] = True
else:
cnf["tracking"] = False
return json.dumps(cnf, cls=numpyEncoder)
[docs] def activate(self):
""" activates tool widget
"""
if self.__xfinal is not None and self.__yfinal is not None:
self._mainwidget.updatePositionMark(
self.__xfinal, self.__yfinal, True)
@QtCore.pyqtSlot(float, float)
def _updateFinal(self, xdata, ydata):
""" updates the final motors position
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
"""
if not self.__moving:
x, y = self._mainwidget.scaledxy(float(xdata), float(ydata))
if x is not None:
self.__xfinal = x
self.__yfinal = y
else:
self.__xfinal = float(xdata)
self.__yfinal = float(ydata)
self.__ui.xLineEdit.setText(str(self.__xfinal))
self.__ui.yLineEdit.setText(str(self.__yfinal))
self.__ui.movePushButton.setToolTip(
"Move to x- and y-motors to (%s, %s)"
% (self.__xfinal, self.__yfinal))
@QtCore.pyqtSlot()
def _moveStopMotors(self):
""" move or stop motors depending on movePushButton
"""
if str(self.__ui.movePushButton.text()) == "Move":
self.__moveMotors()
else:
self.__stopMotors()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _trackStopMotors(self):
""" move or stop motors depending on movePushButton
"""
if str(self.__ui.trackPushButton.text()) == "Track":
self.__trackMotors()
else:
self.__hideMotors()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _finished(self):
""" stop motors
"""
self.__stopMotors()
self._mainwidget.emitTCC()
def __stopMotors(self):
""" move motors
:returns: motors stopped
:rtype: :obj:`bool`
"""
try:
if hasattr(self.__xmotordevice, "stop"):
self.__xmotordevice.stop()
elif hasattr(self.__xmotordevice, "StopMove"):
self.__xmotordevice.StopMove()
else:
return False
if hasattr(self.__ymotordevice, "stop"):
self.__ymotordevice.stop()
elif hasattr(self.__ymotordevice, "StopMove"):
self.__ymotordevice.StopMove()
else:
return False
except Exception as e:
logger.warning(str(e))
# print(str(e))
if self.__motorWatcher:
self.__motorWatcher.motorStatusSignal.disconnect(self._showMotors)
self.__motorWatcher.watchingFinished.disconnect(self._finished)
self.__motorWatcher.stop()
self.__motorWatcher.wait()
self.__motorWatcher = None
if not self.__tracking:
self.parameters.trackinglines = False
self._mainwidget.updateinfowidgets(self.parameters)
self._mainwidget.updateinfowidgets(self.parameters)
self._mainwidget.setDoubleClickLock(False)
self.__ui.movePushButton.setText("Move")
self.__ui.xcurLineEdit.hide()
self.__ui.ycurLineEdit.hide()
self.__ui.takePushButton.show()
self.__ui.axesPushButton.show()
self.__ui.trackPushButton.show()
self.__moving = False
self.__ui.xLineEdit.setReadOnly(False)
self.__ui.yLineEdit.setReadOnly(False)
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
if self.__tracking:
self.__trackMotors()
return True
def __hideMotors(self, changestatus=True):
""" move motors
:returns: motors stopped
:rtype: :obj:`bool`
"""
if self.__motorWatcher:
self.__motorWatcher.motorStatusSignal.disconnect(self._showMotors)
# self.__motorWatcher.watchingFinished.disconnect(self._hide)
self.__motorWatcher.stop()
self.__motorWatcher.wait()
self.__motorWatcher = None
self.parameters.trackinglines = False
self._mainwidget.updateinfowidgets(self.parameters)
# self._mainwidget.setDoubleClickLock(False)
self.__ui.trackPushButton.setText("Track")
self.__ui.xcurLineEdit.hide()
self.__ui.ycurLineEdit.hide()
self.__ui.takePushButton.show()
self.__ui.axesPushButton.show()
self.__ui.trackPushButton.show()
if changestatus:
self.__tracking = False
# self.__ui.xLineEdit.setReadOnly(False)
# self.__ui.yLineEdit.setReadOnly(False)
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
return True
@QtCore.pyqtSlot()
def _getFinal(self):
""" update final positions
"""
try:
self.__xfinal = float(self.__ui.xLineEdit.text())
except Exception:
self.__ui.xLineEdit.setFocus()
return False
try:
self.__yfinal = float(self.__ui.yLineEdit.text())
except Exception:
self.__ui.yLineEdit.setFocus()
return False
self._mainwidget.updatePositionMark(
self.__xfinal, self.__yfinal, True)
def __moveMotors(self):
""" move motors
:returns: motors started
:rtype: :obj:`bool`
"""
self._getFinal()
if self.__xmotordevice is None or self.__ymotordevice is None:
if not self._setMotors():
return False
if str(self.__xmotordevice.state) != "ON" \
and str(self.__ymotordevice.state) != "ON":
try:
self.__xmotordevice.position = self.__xfinal
self.__ymotordevice.position = self.__yfinal
except Exception as e:
logger.warning(str(e))
# print(str(e))
return False
else:
return False
# print("%s %s" % (self.__xfinal, self.__yfinal))
if self.__tracking:
self.__hideMotors(False)
self.parameters.trackinglines = True
self._mainwidget.updateinfowidgets(self.parameters)
self.__motorWatcher = motorWatchThread.MotorWatchThread(
self.__xmotordevice, self.__ymotordevice)
self.__motorWatcher.motorStatusSignal.connect(self._showMotors)
self.__motorWatcher.watchingFinished.connect(self._finished)
self.__motorWatcher.start()
self._mainwidget.setDoubleClickLock(True)
self.__ui.movePushButton.setText("Stop")
self.__ui.xcurLineEdit.show()
self.__ui.ycurLineEdit.show()
self.__ui.takePushButton.hide()
self.__ui.axesPushButton.hide()
self.__ui.trackPushButton.hide()
self.__ui.xLineEdit.setReadOnly(True)
self.__ui.yLineEdit.setReadOnly(True)
self.__moving = True
self.__statex = None
self.__statey = None
return True
def __trackMotors(self):
""" track motors
:returns: motors tracked
:rtype: :obj:`bool`
"""
if self.__xmotordevice is None or self.__ymotordevice is None:
if not self._setMotors():
return False
try:
self.parameters.trackinglines = True
self._mainwidget.updateinfowidgets(self.parameters)
except Exception as e:
logger.warning(str(e))
# print(str(e))
return False
# print("%s %s" % (self.__xfinal, self.__yfinal))
self.__motorWatcher = motorWatchThread.MotorWatchThread(
self.__xmotordevice, self.__ymotordevice)
self.__motorWatcher.motorStatusSignal.connect(self._showMotors)
# self.__motorWatcher.watchingFinished.connect(self._hide)
self.__motorWatcher.start()
self.__ui.trackPushButton.setText("Untrack")
self.__ui.xcurLineEdit.show()
self.__ui.ycurLineEdit.show()
self.__ui.takePushButton.hide()
self.__ui.axesPushButton.hide()
# self.__ui.trackPushButton.hide()
# self.__ui.xLineEdit.setReadOnly(True)
# self.__ui.yLineEdit.setReadOnly(True)
self.__tracking = True
return True
@QtCore.pyqtSlot(float, str, float, str)
def _showMotors(self, positionx, statex, positiony, statey):
""" shows motors positions and states
"""
# print("%s %s %s %s" % (positionx, statex, positiony, statey))
self._mainwidget.updatePositionTrackingMark(
positionx, positiony, True)
self.__ui.xcurLineEdit.setText(str(positionx))
self.__ui.ycurLineEdit.setText(str(positiony))
if self.__statex != statex:
self.__statex = statex
if statex == "MOVING":
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #ADD8E6;")
else:
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
if self.__statey != statey:
self.__statey = statey
if statey == "MOVING":
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #ADD8E6;")
else:
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
@QtCore.pyqtSlot()
def _setMotors(self):
""" launches motors widget
:returns: apply status
:rtype: :obj:`bool`
"""
motors = self._mainwidget.getElementNames("MotorList")
cnfdlg = takeMotorsDialog.TakeMotorsDialog()
if motors is not None:
cnfdlg.motortips = motors
cnfdlg.xmotorname = self.__xmotorname
cnfdlg.ymotorname = self.__ymotorname
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__xmotorname = cnfdlg.xmotorname
self.__ymotorname = cnfdlg.ymotorname
self.__xmotordevice = cnfdlg.xmotordevice
self.__ymotordevice = cnfdlg.ymotordevice
self.__ui.takePushButton.setToolTip(
"x-motor: %s\ny-motor: %s" % (
self.__xmotorname, self.__ymotorname))
self.__ui.xLabel.setToolTip(
"x-motor position (%s)" % self.__xmotorname)
self.__ui.xLineEdit.setToolTip(
"final x-motor position (%s)" % self.__xmotorname)
self.__ui.xcurLineEdit.setToolTip(
"current x-motor position (%s)" % self.__xmotorname)
self.__ui.yLabel.setToolTip(
"y-motor position (%s)" % self.__ymotorname)
self.__ui.yLineEdit.setToolTip(
"final y-motor position (%s)" % self.__ymotorname)
self.__ui.ycurLineEdit.setToolTip(
"current y-motor position (%s)" % self.__ymotorname)
return True
self._mainwidget.emitTCC()
return False
@QtCore.pyqtSlot()
def _message(self):
""" provides intensity message
"""
x, y, intensity = self._mainwidget.currentIntensity()[:3]
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
ilabel = self._mainwidget.scalingLabel()
txdata, tydata = self._mainwidget.scaledxy(x, y)
xunits, yunits = self._mainwidget.axesunits()
if isinstance(intensity, np.ndarray) and \
intensity.size <= 3:
itn = [0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity]
if len(itn) >= 3:
if txdata is not None:
message = "x = %f%s, y = %f%s, " \
"%s = (%.2f, %.2f, %.2f)" % (
txdata,
(" %s" % xunits) if xunits else "",
tydata,
(" %s" % yunits) if yunits else "",
ilabel,
itn[0], itn[1], itn[2])
else:
message = "x = %i%s, y = %i%s, " \
"%s = (%.2f, %.2f, %.2f)" % (
x,
(" %s" % xunits) if xunits else "",
y,
(" %s" % yunits) if yunits else "",
ilabel,
itn[0], itn[1], itn[2])
self._mainwidget.setDisplayedText(message)
else:
if txdata is not None:
message = "x = %f%s, y = %f%s, %s = %.2f" % (
txdata,
(" %s" % xunits) if xunits else "",
tydata,
(" %s" % yunits) if yunits else "",
ilabel,
intensity
)
else:
message = "x = %i%s, y = %i%s, %s = %.2f" % (
x,
(" %s" % xunits) if xunits else "",
y,
(" %s" % yunits) if yunits else "",
ilabel,
intensity)
self._mainwidget.setDisplayedText(message)
class WD(Enum):
""" Enum with Parameter Widget positions
"""
label = 0
read = 1
write = 2
button = 3
[docs]class ParametersToolWidget(ToolBaseWidget):
""" motors tool widget
"""
#: (:obj:`str`) tool name
name = "Parameters"
#: (:obj:`str`) tool name alias
alias = "parameters"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ("TANGO",)
#: (:obj:`dict` <:obj:`str` , :obj:`type` or :obj:`types.MethodType` >) \
#: map of type : converting function
convert = {"float16": float, "float32": float, "float64": float,
"float": float, "int64": long, "int32": int,
"int16": int, "int8": int, "int": int, "uint64": long,
"uint32": long, "uint16": int,
"uint8": int, "uint": int, "string": str, "str": str,
"bool": Converters.toBool}
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
# #: (:obj:`list` <str, str, str>)
# detector parameters (label, devicename, type)
self.__detparams = []
#: (:obj:`list` <:class:`tango.DeviceProxy`>) attribute proxies
self.__aproxies = []
#: (:obj:`list` <:list:`any`>) attribute values
self.__avalues = []
#: (:class:`lavuelib.motorWatchThread.attributeWatchThread`)
# attribute watcher
self.__attrWatcher = None
#: (:obj:`bool`) is watching
self.__watching = False
#: (:obj:`list` <:obj:`list` <:class:`pyqtgraph.QtCore.QObject`> >)
# widgets
self.__widgets = []
#: (:class:`Ui_ParametersToolWidget')
#: ui_toolwidget object from qtdesigner
self.__ui = _parametersformclass()
self.__ui.setupUi(self)
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.setupPushButton.clicked, self._setParameters],
[self._mainwidget.mouseImagePositionChanged, self._message],
]
#: (:class:`pyqtgraph.QtCore.QSignalMapper`) apply mapper
self.__applymapper = QtCore.QSignalMapper(self)
self.__applymapper.mapped.connect(self._applypars)
[docs] @debugmethod
def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "tango_det_attrs" in cnf.keys():
record = cnf["tango_det_attrs"]
if self.__settings.tangodetattrs != \
str(json.dumps(record, cls=numpyEncoder)):
self.__settings.tangodetattrs = \
str(json.dumps(record, cls=numpyEncoder))
self.__updateParams()
[docs] @debugmethod
def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
try:
cnf["tango_det_attrs"] = json.loads(self.__settings.tangodetattrs)
except Exception as e:
logger.warning(str(e))
return json.dumps(cnf, cls=numpyEncoder)
def _applypars(self, wid):
""" apply the parameter with the given widget id
:param wid: widget id
:type wid: :obj:`int`
"""
txt = str(self.__widgets[wid][WD.write.value].text() or "")
tp = self.__detparams[wid][2]
frm = self.__detparams[wid][4]
ap = self.__aproxies[wid]
if frm == "SPECTRUM":
try:
vl = json.loads(txt)
except Exception as e:
try:
vl = txt.split()
if tp and tp in self.convert.keys():
vl = [self.convert[tp](v) for v in vl]
except Exception as e2:
logger.warning(str(e))
logger.warning(str(e2))
vl = txt
elif frm == "IMAGE":
try:
vl = json.loads(txt)
except Exception as e:
logger.warning(str(e))
vl = txt
else:
try:
if tp and tp in self.convert.keys():
vl = self.convert[tp](txt)
else:
vl = txt
except Exception as e:
logger.warning(str(e))
vl = txt
try:
ap.write(vl)
except Exception as e:
logger.warning(str(e))
[docs] @debugmethod
def activate(self):
""" activates tool widget
"""
record = json.loads(str(self.__settings.tangodetattrs or "{}").strip())
if not isinstance(record, dict):
record = {}
self.deactivate()
for lb in sorted(record.keys()):
try:
dv = record[lb]
ap = tango.AttributeProxy(dv)
unit = ""
frm = ""
try:
cap = ap.get_config()
unit = cap.unit or ""
vl = ap.read().value
tp = type(vl).__name__
if tp == "ndarray":
tp = str(vl.dtype)
frm = str(cap.data_format)
except Exception as e:
logger.warning(str(e))
vl = None
tp = ""
except Exception as e:
logger.warning(str(e))
dv = None
if dv is not None:
self.__aproxies.append(ap)
self.__detparams.append([lb, dv, tp, unit, frm])
self.__avalues.append(vl)
self.__updateWidgets()
self.__attrWatcher = motorWatchThread.AttributeWatchThread(
self.__aproxies, self.__settings.toolpollinginterval)
self.__attrWatcher.attrValuesSignal.connect(self._showValues)
self.__attrWatcher.start()
while not self.__attrWatcher.isWatching():
QtCore.QCoreApplication.processEvents()
time.sleep(0.1)
def __updateWidgets(self):
""" add widgets
"""
layout = self.__ui.parGridLayout
while len(self.__detparams) > len(self.__widgets):
self.__widgets.append(
[
QtWidgets.QLabel(parent=self._mainwidget),
QtWidgets.QLineEdit(parent=self._mainwidget),
QtWidgets.QLineEdit(parent=self._mainwidget),
QtWidgets.QPushButton("Apply", parent=self._mainwidget),
]
)
last = len(self.__widgets)
self.__widgets[-1][WD.read.value].setReadOnly(True)
self.__widgets[-1][WD.read.value].setStyleSheet(
"color: black; background-color: #90EE90;")
self.__widgets[-1][WD.read.value].setAlignment(
QtCore.Qt.AlignCenter)
self.__widgets[-1][WD.write.value].setAlignment(
QtCore.Qt.AlignRight)
for i, w in enumerate(self.__widgets[-1]):
layout.addWidget(w, last, i)
self.__widgets[-1][WD.button.value].clicked.connect(
self.__applymapper.map)
self.__applymapper.setMapping(
self.__widgets[-1][WD.button.value], last - 1)
# self.__widgets[-1][WD.read.value].setMaximumWidth(200)
while len(self.__detparams) < len(self.__widgets):
wds = self.__widgets.pop()
self.__applymapper.removeMappings(wds[WD.button.value])
for w in wds:
w.hide()
layout.removeWidget(w)
for i, pars in enumerate(self.__detparams):
self.__widgets[i][WD.label.value].setText("%s:" % pars[0])
self.__widgets[i][WD.label.value].setToolTip("%s" % pars[1])
self.__widgets[i][WD.write.value].setText("")
self.__widgets[i][WD.write.value].setToolTip("%s" % pars[1])
self.__widgets[i][WD.write.button.value].setToolTip(
"Write the new value of %s" % pars[0])
if self.__avalues[i] is None:
vl = ""
else:
vl = str(self.__avalues[i])
if pars[3]:
vl = "%s %s" % (vl, pars[3])
self.__widgets[i][WD.read.value].setToolTip(vl)
self.__widgets[i][WD.read.value].setText(vl)
[docs] @debugmethod
def deactivate(self):
""" activates tool widget
"""
if self.__attrWatcher:
self.__attrWatcher.attrValuesSignal.disconnect(self._showValues)
# self.__attrWatcher.watchingFinished.disconnect(self._finished)
logger.debug("STOPING %s" % str(self.__attrWatcher))
self.__attrWatcher.stop()
logger.debug("WAITING for %s" % str(self.__attrWatcher))
self.__attrWatcher.wait()
logger.debug("REMOVING for %s" % str(self.__attrWatcher))
self.__attrWatcher = None
self.__aproxies = []
self.__detparams = []
self.__avalues = []
@QtCore.pyqtSlot(str)
def _showValues(self, values):
""" show values
"""
vls = json.loads(values)
for i, pars in enumerate(self.__widgets):
if i < len(vls):
vl = str(vls[i])
if self.__detparams[i][3]:
vl = "%s %s" % (vl, self.__detparams[i][3])
self.__widgets[i][WD.read.value].setText(vl)
self.__widgets[i][WD.read.value].setToolTip(vl)
@QtCore.pyqtSlot()
def _setParameters(self):
""" launches parameters widget
:returns: apply status
:rtype: :obj:`bool`
"""
record = json.loads(str(self.__settings.tangodetattrs or "{}").strip())
if not isinstance(record, dict):
record = {}
dform = edDictDialog.EdDictDialog(self)
dform.record = record
dform.title = "Tango Detector Attributes"
dform.createGUI()
dform.exec_()
if dform.dirty:
for key in list(record.keys()):
if not str(key).strip():
record.pop(key)
if self.__settings.tangodetattrs != \
str(json.dumps(record, cls=numpyEncoder)):
self.__settings.tangodetattrs = \
str(json.dumps(record, cls=numpyEncoder))
self.__updateParams()
def __updateParams(self):
""" update parameters
"""
self.deactivate()
self.activate()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _message(self):
""" provides intensity message
"""
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
ilabel = self._mainwidget.scalingLabel()
txdata, tydata = self._mainwidget.scaledxy(x, y)
xunits, yunits = self._mainwidget.axesunits()
if txdata is not None:
message = "x = %f%s, y = %f%s, %s = %.2f" % (
txdata,
(" %s" % xunits) if xunits else "",
tydata,
(" %s" % yunits) if yunits else "",
ilabel,
intensity
)
else:
message = "x = %f%s, y = %f%s, %s = %.2f" % (
x,
(" %s" % xunits) if xunits else "",
y,
(" %s" % yunits) if yunits else "",
ilabel,
intensity)
self._mainwidget.setDisplayedText(message)
[docs]class MeshToolWidget(ToolBaseWidget):
""" mesh tool widget
"""
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) roi info Changed signal
roiInfoChanged = QtCore.pyqtSignal(str)
#: (:obj:`str`) tool name
name = "MeshScan"
#: (:obj:`str`) tool name alias
alias = "meshscan"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ("TANGO",)
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`str`) x-motor name
self.__xmotorname = ""
#: (:obj:`str`) y-motor name
self.__ymotorname = ""
#: (:obj:`str`) state of x-motor
self.__statex = None
#: (:obj:`str`) state of y-motor
self.__statey = None
#: (:class:`tango.DeviceProxy`) x-motor device
self.__xmotordevice = None
#: (:class:`tango.DeviceProxy`) y-motor device
self.__ymotordevice = None
#: (:class:`tango.DeviceProxy`) door server
self.__door = None
#: (:class:`lavuelib.motorWatchThread.motorWatchThread`) motor watcher
self.__motorWatcher = None
#: (:obj:`bool`) is moving
self.__moving = False
#: (:obj:`int`) number of x intervals
self.__xintervals = 2
#: (:obj:`int`) number of y intervals
self.__yintervals = 3
#: (:obj:`float`) integration time in seconds
self.__itime = 0.1
#: (:class:`Ui_MeshToolWidget')
#: ui_toolwidget object from qtdesigner
self.__ui = _meshformclass()
self.__ui.setupUi(self)
self.__showLabels()
self.parameters.scale = True
self.parameters.rois = False
self.parameters.mesh = True
self.parameters.infolineedit = ""
self.parameters.infolabel = "[x1, y1, x2, y2]: "
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.axesPushButton.clicked, self._mainwidget.setTicks],
[self.__ui.takePushButton.clicked, self._setMotors],
[self.__ui.intervalsPushButton.clicked, self._setIntervals],
[self.__ui.scanPushButton.clicked, self._scanStopMotors],
[self.roiInfoChanged, self._mainwidget.updateDisplayedText],
[self._mainwidget.roiValueChanged, self.updateROIDisplayText],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
pars = ["position", "scale",
"xtext", "ytext", "xunits", "yunits"]
if any(par in cnf.keys() for par in pars):
self._mainwidget.updateTicks(cnf)
if "motors" in cnf.keys():
try:
motorname = cnf["motors"][0]
motordevice = tango.DeviceProxy(motorname)
for attr in ["state", "position"]:
if not hasattr(motordevice, attr):
raise Exception("Missing %s" % attr)
self.__xmotorname = motorname
self.__xmotordevice = motordevice
except Exception as e:
logger.warning(str(e))
try:
motorname = cnf["motors"][1]
motordevice = tango.DeviceProxy(motorname)
for attr in ["state", "position"]:
if not hasattr(motordevice, attr):
raise Exception("Missing %s" % attr)
self.__ymotorname = motorname
self.__ymotordevice = motordevice
except Exception as e:
logger.warning(str(e))
pars = ["x_intervals", "y_intervals", "interval_time"]
if any(par in cnf.keys() for par in pars):
if "x_intervals" in cnf.keys():
try:
self.__xintervals = int(cnf["x_intervals"])
except Exception:
pass
if "y_intervals" in cnf.keys():
try:
self.__yintervals = int(cnf["y_intervals"])
except Exception:
pass
if "interval_time" in cnf.keys():
try:
self.__itime = float(cnf["interval_time"])
except Exception:
pass
self._updateIntervals()
if "scan" in cnf.keys():
if cnf["scan"]:
if str(self.__ui.scanPushButton.text()) == "Scan":
self._scanStopMotors()
if "stop" in cnf.keys():
if cnf["stop"]:
if str(self.__ui.scanPushButton.text()) == "Stop":
self._scanStopMotors()
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
xpos, ypos, xsc, ysc = self._mainwidget.scale()
cnf["xunits"], cnf["yunits"] = self._mainwidget.axesunits()
cnf["xtext"], cnf["ytext"] = self._mainwidget.axestext()
cnf["position"] = [xpos, ypos]
cnf["scale"] = [xsc, ysc]
cnf["x_intervals"] = self.__xintervals
cnf["y_intervals"] = self.__yintervals
cnf["interval_time"] = self.__itime
cnf["motors"] = [self.__xmotorname, self.__ymotorname]
if str(self.__ui.scanPushButton.text()) == "Scan":
cnf["motor_state"] = "ON"
else:
cnf["motor_state"] = "MOVING"
return json.dumps(cnf, cls=numpyEncoder)
# self._mainwidget.updateROIs(1)
[docs] def deactivate(self):
""" deactivates tool widget
"""
self._mainwidget.meshCoordsChanged.emit()
[docs] @QtCore.pyqtSlot(str, int, str)
def updateROIDisplayText(self, text, currentroi, roiVal):
""" updates ROI display text
:param text: standard display text
:type text: :obj:`str`
:param currentroi: current roi label
:type currentroi: :obj:`str`
:param text: roi sum value
:type text: :obj:`str`
"""
if "/" in roiVal:
sroiv = " / ".split(roiVal)
roiVal = sroiv[currentroi] if len(sroiv) > currentroi else ""
roilabel = "roi [%s]" % (currentroi + 1)
self.roiInfoChanged.emit("%s, %s = %s" % (text, roilabel, roiVal))
@QtCore.pyqtSlot()
def _scanStopMotors(self):
""" starts or stops scan
"""
if str(self.__ui.scanPushButton.text()) == "Scan":
self.__startScan()
else:
self.__stopScan()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _finished(self):
""" stops mesh scan without stopping the macro
"""
self.__stopScan(stopmacro=False)
self._mainwidget.emitTCC()
def __stopScan(self, stopmacro=True):
""" stops mesh scan
:param stopmacro: call stopmacro
:type stopmacro: :obj:`bool`
:returns: motors stopped
:rtype: :obj:`bool`
"""
if stopmacro:
try:
self.__door.StopMacro()
except Exception as e:
logger.warning(str(e))
# print(str(e))
if self.__motorWatcher:
self.__motorWatcher.motorStatusSignal.disconnect(self._showMotors)
self.__motorWatcher.watchingFinished.disconnect(self._finished)
self.__motorWatcher.stop()
self.__motorWatcher.wait()
self.__motorWatcher = None
self.__showLabels()
self.__moving = False
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
self._mainwidget.showDoorError()
return True
def __showLabels(self):
""" shows GUI labels
"""
self.__ui.scanPushButton.setText("Scan")
self.__ui.xcurLineEdit.hide()
self.__ui.ycurLineEdit.hide()
self.__ui.takePushButton.show()
self.__ui.axesPushButton.show()
self.__ui.intervalsPushButton.show()
self.__ui.xLabel.setText("X: %s" % (self.__xintervals))
self.__ui.yLabel.setText("Y: %s" % (self.__yintervals))
self.__ui.timeLabel.setText("dT: %ss" % str(self.__itime))
self.__ui.timeLabel.show()
def __hideLabels(self):
""" hides GUI labels
"""
self.__ui.scanPushButton.setText("Stop")
self.__ui.xcurLineEdit.show()
self.__ui.ycurLineEdit.show()
self.__ui.takePushButton.hide()
self.__ui.axesPushButton.hide()
self.__ui.intervalsPushButton.hide()
self.__ui.xLabel.setText("X: %s" % (self.__xintervals))
self.__ui.yLabel.setText("Y: %s" % (self.__yintervals))
self.__ui.timeLabel.setText("dT: %ss" % str(self.__itime))
self.__ui.timeLabel.show()
def __startScan(self):
""" start scan
:returns: motors started
:rtype: :obj:`bool`
"""
coords = self._mainwidget.meshCoords()
if 0 < len(coords):
curcoords = coords[0]
else:
return False
if self.__xmotordevice is None or self.__ymotordevice is None:
if not self._setMotors():
return False
macrocommand = []
macrocommand.append("mesh")
macrocommand.append(str(self.__xmotorname))
macrocommand.append(str(float(curcoords[0])))
macrocommand.append(str(float(curcoords[2])))
macrocommand.append(str(self.__xintervals))
macrocommand.append(str(self.__ymotorname))
macrocommand.append(str(float(curcoords[1])))
macrocommand.append(str(float(curcoords[3])))
macrocommand.append(str(self.__yintervals))
macrocommand.append(str(self.__itime))
macrocommand.append("True")
self.__door = self._mainwidget.getDoor()
if self.__door is None:
logger.error(
"MeshToolWidget.__startScan: Cannot access Door device")
return False
if not self._mainwidget.runMacro(macrocommand):
logger.error(
"MeshToolWidget.__startScan: Cannot in running %s"
% macrocommand)
return False
self.__motorWatcher = motorWatchThread.MotorWatchThread(
self.__xmotordevice, self.__ymotordevice, self.__door)
self.__motorWatcher.motorStatusSignal.connect(self._showMotors)
self.__motorWatcher.watchingFinished.connect(self._finished)
self.__motorWatcher.start()
self.__hideLabels()
self.__moving = True
self.__statex = None
self.__statey = None
return True
@QtCore.pyqtSlot(float, str, float, str)
def _showMotors(self, positionx, statex, positiony, statey):
""" shows motors positions and states
"""
# print("%s %s %s %s" % (positionx, statex, positiony, statey))
self.__ui.xcurLineEdit.setText(str(positionx))
self.__ui.ycurLineEdit.setText(str(positiony))
if self.__statex != statex:
self.__statex = statex
if statex == "MOVING":
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #ADD8E6;")
else:
self.__ui.xcurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
if self.__statey != statey:
self.__statey = statey
if statey == "MOVING":
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #ADD8E6;")
else:
self.__ui.ycurLineEdit.setStyleSheet(
"color: black; background-color: #90EE90;")
@QtCore.pyqtSlot()
def _setMotors(self):
""" launches motors widget
:returns: apply status
:rtype: :obj:`bool`
"""
motors = self._mainwidget.getElementNames("MotorList")
cnfdlg = takeMotorsDialog.TakeMotorsDialog()
if motors is not None:
cnfdlg.motortips = motors
cnfdlg.title = "Motor aliases"
cnfdlg.xmotorname = self.__xmotorname
cnfdlg.ymotorname = self.__ymotorname
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__xmotorname = cnfdlg.xmotorname
self.__ymotorname = cnfdlg.ymotorname
self.__xmotordevice = cnfdlg.xmotordevice
self.__ymotordevice = cnfdlg.ymotordevice
self.__ui.takePushButton.setToolTip(
"x-motor: %s\ny-motor: %s" % (
self.__xmotorname, self.__ymotorname))
self.__ui.xLabel.setToolTip(
"x-motor interval number (%s)" % self.__xmotorname)
self.__ui.xcurLineEdit.setToolTip(
"current x-motor position (%s)" % self.__xmotorname)
self.__ui.yLabel.setToolTip(
"y-motor interval number (%s)" % self.__ymotorname)
self.__ui.ycurLineEdit.setToolTip(
"current y-motor position (%s)" % self.__ymotorname)
self.__showLabels()
self._mainwidget.emitTCC()
return True
return False
@QtCore.pyqtSlot()
def _setIntervals(self):
""" launches motors widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = intervalsDialog.IntervalsDialog()
cnfdlg.xintervals = self.__xintervals
cnfdlg.yintervals = self.__yintervals
cnfdlg.itime = self.__itime
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__xintervals = cnfdlg.xintervals
self.__yintervals = cnfdlg.yintervals
self.__itime = cnfdlg.itime
self._updateIntervals()
return True
return False
def _updateIntervals(self):
""" update interval informations
"""
self.__ui.intervalsPushButton.setToolTip(
"x-intervals:%s\ny-intervals:%s\nintegration time:%s" % (
self.__xintervals, self.__yintervals, self.__itime))
self.__showLabels()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _message(self):
""" provides mesh message
"""
message = ""
coords = self._mainwidget.meshCoords()
if 0 < len(coords):
message = "%s" % coords[0]
self._mainwidget.setDisplayedText(message)
[docs]class ROIToolWidget(ToolBaseWidget):
""" roi tool widget
"""
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) apply ROI pressed signal
applyROIPressed = QtCore.pyqtSignal(str, int)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) fetch ROI pressed signal
fetchROIPressed = QtCore.pyqtSignal(str)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) roi info Changed signal
roiInfoChanged = QtCore.pyqtSignal(str)
#: (:obj:`str`) tool name
name = "ROI"
#: (:obj:`str`) tool name alias
alias = "roi"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`Ui_ROIToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _roiformclass()
self.__ui.setupUi(self)
#: (:obj:`list`< :obj:`str`>) sardana aliases
self.__aliases = []
#: (:obj:`int`) ROI label length
self.__textlength = 0
self.parameters.rois = True
self.parameters.infolineedit = ""
self.parameters.infolabel = "[x1, y1, x2, y2], sum: "
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self._updateApplyButton()
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.applyROIPushButton.clicked, self._emitApplyROIPressed],
[self.__ui.fetchROIPushButton.clicked, self._emitFetchROIPressed],
[self.applyROIPressed, self._mainwidget.applyROIs],
[self.fetchROIPressed, self._mainwidget.fetchROIs],
[self.roiInfoChanged, self._mainwidget.updateDisplayedText],
[self.__ui.labelROILineEdit.textChanged,
self._updateApplyButton],
[self.__ui.roiSpinBox.valueChanged, self._mainwidget.updateROIs],
[self.__ui.roiSpinBox.valueChanged, self._mainwidget.emitTCC],
[self.__ui.roiSpinBox.valueChanged,
self._mainwidget.writeDetectorROIsAttribute],
[self.__ui.labelROILineEdit.textEdited, self._writeDetectorROIs],
[self.__ui.labelROILineEdit.textEdited, self._mainwidget.emitTCC],
[self._mainwidget.roiLineEditChanged, self._updateApplyButton],
[self._mainwidget.roiAliasesChanged, self.updateROILineEdit],
[self._mainwidget.roiValueChanged, self.updateROIDisplayText],
[self._mainwidget.roiNumberChanged, self.setROIsNumber],
# [self._mainwidget.sardanaEnabled, self.updateROIButton],
[self._mainwidget.mouseImagePositionChanged, self._message],
]
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "aliases" in cnf.keys():
aliases = cnf["aliases"]
if isinstance(aliases, list):
aliases = " ".join(aliases)
self.__ui.labelROILineEdit.setText(aliases)
if "rois_number" in cnf.keys():
try:
self.__ui.roiSpinBox.setValue(int(cnf["rois_number"]))
except Exception as e:
logger.warning(str(e))
# print(str(e))
if "rois_coords" in cnf.keys():
self._mainwidget.updateROIs(
len(cnf["rois_coords"]), cnf["rois_coords"])
if "apply" in cnf.keys():
if cnf["apply"]:
self._emitApplyROIPressed()
if "fetch" in cnf.keys():
if cnf["fetch"]:
self._emitFetchROIPressed()
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["aliases"] = str(self.__ui.labelROILineEdit.text()).split(" ")
cnf["rois_number"] = self.__ui.roiSpinBox.value()
cnf["rois_coords"] = self._mainwidget.roiCoords()
return json.dumps(cnf, cls=numpyEncoder)
[docs] def activate(self):
""" activates tool widget
"""
self._mainwidget.changeROIRegion()
self.setROIsNumber(len(self._mainwidget.roiCoords()))
self.__aliases = self._mainwidget.getElementNames("ExpChannelList")
self.updateROILineEdit(self._mainwidget.roilabels)
self.__updateCompleter()
# self.updateROIButton(
# self.__settings.sardana or bool(self.__settings.analysisdevice))
def __updateCompleter(self):
""" updates the labelROI help
"""
text = str(self.__ui.labelROILineEdit.text())
sttext = text.strip()
sptext = sttext.split()
stext = ""
if text.endswith(" "):
stext = sttext
elif len(sptext) > 1:
stext = " ".join(sptext[:-1])
if stext:
if self.__aliases:
hints = ["%s %s" % (stext, al) for al in self.__aliases]
else:
hints = [stext]
else:
hints = self.__aliases or []
completer = QtWidgets.QCompleter(hints, self)
self.__ui.labelROILineEdit.setCompleter(completer)
[docs] def deactivate(self):
""" deactivates tool widget
"""
self._mainwidget.roiCoordsChanged.emit()
@QtCore.pyqtSlot()
def _writeDetectorROIs(self):
""" writes Detector rois and updates roi labels
"""
self._mainwidget.roilabels = str(self.__ui.labelROILineEdit.text())
self._mainwidget.writeDetectorROIsAttribute()
@QtCore.pyqtSlot()
def _message(self):
""" provides roi message
"""
message = ""
current = self._mainwidget.currentROI()
coords = self._mainwidget.roiCoords()
if current > -1 and current < len(coords):
message = "%s" % coords[current]
self._mainwidget.setDisplayedText(message)
@QtCore.pyqtSlot()
def _emitApplyROIPressed(self):
""" emits applyROIPressed signal"""
text = str(self.__ui.labelROILineEdit.text()).strip()
if self.__settings.singlerois:
slabel = re.split(';|,| |\n', str(text))
slabel = [lb for lb in slabel if lb]
lsl = len(slabel)
while lsl < len(self._mainwidget.roiCoords()):
lsl += 1
slabel.append("roi%s" % lsl)
self.__ui.labelROILineEdit.setText(" ".join(slabel))
self._updateApplyButton()
text = str(self.__ui.labelROILineEdit.text()).strip()
if not text:
self.__ui.labelROILineEdit.setText("rois")
self._updateApplyButton()
text = str(self.__ui.labelROILineEdit.text()).strip()
roispin = int(self.__ui.roiSpinBox.value())
self.applyROIPressed.emit(text, roispin)
@QtCore.pyqtSlot()
def _emitFetchROIPressed(self):
""" emits fetchROIPressed signal"""
text = str(self.__ui.labelROILineEdit.text())
self.fetchROIPressed.emit(text)
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateApplyButton(self):
""" updates applied button"""
stext = str(self.__ui.labelROILineEdit.text())
self._mainwidget.roilabels = stext
currentlength = len(stext)
# if not stext.strip():
# self.__ui.applyROIPushButton.setEnabled(False)
self.__updateCompleter()
# else:
# # self.__ui.applyROIPushButton.setEnabled(True)
if stext.endswith(" ") or currentlength < self.__textlength:
self.__updateCompleter()
self.__textlength = currentlength
[docs] @QtCore.pyqtSlot(str)
def updateROILineEdit(self, text):
""" updates ROI line edit text
:param text: text to update
:type text: :obj:`str`
"""
if not self.__ui.labelROILineEdit.hasFocus():
self.__ui.labelROILineEdit.setText(text)
self._updateApplyButton()
[docs] @QtCore.pyqtSlot(bool)
def updateROIButton(self, enabled):
""" enables/disables ROI buttons
:param enable: buttons enabled
:type enable: :obj:`bool`
"""
# self.__ui.applyROIPushButton.setEnabled(enabled)
# self.__ui.fetchROIPushButton.setEnabled(enabled)
[docs] @QtCore.pyqtSlot(int)
def updateApplyTips(self, rid):
""" updates apply tips
:param rid: current roi id
:type rid: :obj:`int`
"""
if rid < 0:
self.__ui.applyROIPushButton.setToolTip(
"remove ROI aliases from the Door environment"
" as well as from Active MntGrp")
else:
self.__ui.applyROIPushButton.setToolTip(
"add ROI aliases to the Door environment "
"as well as to Active MntGrp")
[docs] @QtCore.pyqtSlot(str, int, str)
def updateROIDisplayText(self, text, currentroi, roiVal):
""" updates ROI display text
:param text: standard display text
:type text: :obj:`str`
:param currentroi: current roi label
:type currentroi: :obj:`str`
:param text: roi sum value
:type text: :obj:`str`
"""
roilabel = "roi [%s]" % (currentroi + 1)
slabel = []
rlabel = str(self.__ui.labelROILineEdit.text())
if rlabel:
slabel = re.split(';|,| |\n', rlabel)
slabel = [lb for lb in slabel if lb]
if slabel:
roilabel = "%s [%s]" % (
slabel[currentroi]
if currentroi < len(slabel) else slabel[-1],
(currentroi + 1)
)
if "/" in roiVal:
self.roiInfoChanged.emit(
"%s, %s; values = %s" % (text, roilabel, roiVal))
else:
self.roiInfoChanged.emit("%s, %s = %s" % (text, roilabel, roiVal))
[docs] @QtCore.pyqtSlot(int)
def setROIsNumber(self, rid):
"""sets a number of rois
:param rid: number of rois
:type rid: :obj:`int`
"""
self.__ui.roiSpinBox.setValue(rid)
[docs]class LineCutToolWidget(ToolBaseWidget):
""" line-cut tool widget
"""
#: (:obj:`str`) tool name
name = "LineCut"
#: (:obj:`str`) tool name alias
alias = "linecut"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`Ui_LineCutToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _cutformclass()
self.__ui.setupUi(self)
self.parameters.cuts = True
self.parameters.bottomplot = True
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`int`) 1d x-coorindate index,
#: i.e. {0:Points, 1:"X-Pixels", 2:"Y-Pixels"}
self.__xindex = 0
#: (:obj:`bool`) plot cuts
self.__allcuts = False
#: (:obj:`list`<:class:`pyqtgraph.PlotDataItem`>) 1D plot freezed
self.__freezed = []
#: (:obj:`list`<:class:`pyqtgraph.PlotDataItem`>) 1D plot
self.__curves = []
#: (:obj:`int`) current plot number
self.__nrplots = 0
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.cutSpinBox.valueChanged, self._updateCuts],
[self.__ui.cutSpinBox.valueChanged, self._mainwidget.emitTCC],
[self._mainwidget.cutNumberChanged, self._setCutsNumber],
[self._mainwidget.cutCoordsChanged, self._plotCuts],
[self.__ui.xcoordsComboBox.currentIndexChanged,
self._setXCoords],
[self.__ui.xcoordsComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self._mainwidget.mouseImagePositionChanged, self._message],
[self.__ui.allcutsCheckBox.stateChanged, self._updateAllCuts],
[self.__ui.allcutsCheckBox.stateChanged,
self._mainwidget.emitTCC],
[self._mainwidget.freezeBottomPlotClicked, self._freezeplot],
[self._mainwidget.clearBottomPlotClicked, self._clearplot],
]
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "cuts_number" in cnf.keys():
try:
self.__ui.cutSpinBox.setValue(int(cnf["cuts_number"]))
except Exception as e:
logger.warning(str(e))
# print(str(e))
if "all_cuts" in cnf.keys():
self.__ui.allcutsCheckBox.setChecked(bool(cnf["all_cuts"]))
if "x_coordinates" in cnf.keys():
idxs = ["points", "x-pixels", "y-pixels"]
xcrd = str(cnf["x_coordinates"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.xcoordsComboBox.setCurrentIndex(idx)
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["x_coordinates"] = str(
self.__ui.xcoordsComboBox.currentText()).lower()
cnf["all_cuts"] = self.__ui.allcutsCheckBox.isChecked()
cnf["cuts_number"] = self.__ui.cutSpinBox.value()
return json.dumps(cnf, cls=numpyEncoder)
@QtCore.pyqtSlot()
def _freezeplot(self):
""" freeze plot
"""
self._clearplot()
nrplots = self.__nrplots
while nrplots > len(self.__freezed):
cr = self._mainwidget.onedbottomplot()
cr.setPen(_pg.mkColor(0.5))
self.__freezed.append(cr)
for i in range(nrplots):
dt = self.__curves[i].xData, self.__curves[i].yData
self.__freezed[i].setData(*dt)
self.__freezed[i].show()
self.__freezed[i].setVisible(True)
for i in range(nrplots, len(self.__freezed)):
self.__freezed[i].hide()
self.__freezed[i].setVisible(True)
@QtCore.pyqtSlot()
def _clearplot(self):
""" clear plot
"""
for cr in self.__freezed:
cr.setVisible(False)
@QtCore.pyqtSlot(int)
def _updateCuts(self, cid):
""" update Cuts
:param cid: cut id
:type cid: :obj:`int`
"""
if self.__allcuts:
self.__allcuts = True
else:
self.__allcuts = False
self._mainwidget.updateCuts(cid)
[docs] def activate(self):
""" activates tool widget
"""
if not self.__curves:
self.__curves.append(self._mainwidget.onedbottomplot(True))
self.__nrplots = 1
for curve in self.__curves:
curve.show()
curve.setVisible(True)
self._updateAllCuts(self.__allcuts)
self._plotCuts()
self._mainwidget.bottomplotShowMenu(True, True)
[docs] def deactivate(self):
""" activates tool widget
"""
self._mainwidget.bottomplotShowMenu()
for curve in self.__curves:
curve.hide()
curve.setVisible(False)
self._mainwidget.removebottomplot(curve)
self.__curves = []
for freezed in self.__freezed:
freezed.hide()
freezed.setVisible(False)
self._mainwidget.removebottomplot(freezed)
self.__freezed = []
@QtCore.pyqtSlot(int)
def _updateAllCuts(self, value):
""" updates X row status
:param value: if True or not 0 x-cooridnates taken from the first row
:param value: :obj:`int` or :obj:`bool`
"""
self.__allcuts = value
self._updateCuts(self.__ui.cutSpinBox.value())
@QtCore.pyqtSlot(int)
def _setXCoords(self, xindex):
""" sets x-coodinates for 1d plot
:param xindex: 1d x-coorindate index,
:type xindex: :obj:`int`
"""
self.__xindex = xindex
self._plotCuts()
@QtCore.pyqtSlot()
def _plotCuts(self):
""" plots the current 1d Cut
"""
if self.__allcuts:
self._plotAllCuts()
else:
self._plotCut()
def _plotAllCuts(self):
""" plot all 1d Cuts
"""
if self._mainwidget.currentTool() == self.name:
if self.__settings.sendresults:
xl = []
yl = []
nrplots = self.__ui.cutSpinBox.value()
if self.__nrplots != nrplots:
while nrplots > len(self.__curves):
self.__curves.append(self._mainwidget.onedbottomplot())
for i in range(nrplots):
self.__curves[i].show()
for i in range(nrplots, len(self.__curves)):
self.__curves[i].hide()
self.__nrplots = nrplots
if nrplots:
for i, cr in enumerate(self.__curves):
if i < nrplots:
cr.setPen(_pg.hsvColor(i/float(nrplots)))
coords = self._mainwidget.cutCoords()
rws = self._mainwidget.rangeWindowScale()
for i in range(nrplots):
dt = self._mainwidget.cutData(i)
if dt is not None:
if self.__settings.nanmask:
if dt.dtype.kind == 'f' and np.isnan(dt.min()):
dt = np.nan_to_num(dt)
if self.__xindex:
if i < len(coords):
crds = coords[i]
else:
crds = [0, 0, 1, 1, 0.00001]
if self.__xindex == 2:
dx = np.linspace(crds[1], crds[3], len(dt))
else:
dx = np.linspace(crds[0], crds[2], len(dt))
self.__curves[i].setData(x=dx, y=dt)
if self.__settings.sendresults:
xl.append([float(e) for e in dx])
yl.append([float(e) for e in dt])
else:
if rws > 1.0:
dx = np.linspace(0, len(dt - 1) * rws, len(dt))
self.__curves[i].setData(x=dx, y=dt)
if self.__settings.sendresults:
xl.append([float(e) for e in dx])
yl.append([float(e) for e in dt])
else:
self.__curves[i].setData(y=dt)
if self.__settings.sendresults:
xl.append(list(range(len(dt))))
yl.append([float(e) for e in dt])
self.__curves[i].setVisible(True)
else:
self.__curves[i].setVisible(False)
if self.__settings.sendresults:
self.__sendresults(xl, yl)
def _plotCut(self):
""" plot the current 1d Cut
"""
if self.__nrplots > 1:
for i in range(1, len(self.__curves)):
self.__curves[i].setVisible(False)
self.__curves[i].hide()
self.__nrplots = 1
if self._mainwidget.currentTool() == self.name:
if self.__settings.sendresults:
xl = []
yl = []
dt = self._mainwidget.cutData()
self.__curves[0].setPen(_pg.mkColor('r'))
if dt is not None:
if self.__settings.nanmask:
if dt.dtype.kind == 'f' and np.isnan(dt.min()):
dt = np.nan_to_num(dt)
if self.__xindex:
crds = [0, 0, 1, 1, 0.00001]
if self._mainwidget.currentCut() > -1:
crds = self._mainwidget.cutCoords()[
self._mainwidget.currentCut()]
if self.__xindex == 2:
dx = np.linspace(crds[1], crds[3], len(dt))
else:
dx = np.linspace(crds[0], crds[2], len(dt))
self.__curves[0].setData(x=dx, y=dt)
if self.__settings.sendresults:
xl.append([float(e) for e in dx])
yl.append([float(e) for e in dt])
else:
rws = self._mainwidget.rangeWindowScale()
if rws > 1.0:
dx = np.linspace(0, len(dt - 1) * rws, len(dt))
self.__curves[0].setData(x=dx, y=dt)
if self.__settings.sendresults:
xl.append([float(e) for e in dx])
yl.append([float(e) for e in dt])
else:
self.__curves[0].setData(y=dt)
if self.__settings.sendresults:
xl.append(list(range(len(dt))))
yl.append([float(e) for e in dt])
self.__curves[0].setVisible(True)
else:
self.__curves[0].setVisible(False)
if self.__settings.sendresults:
self.__sendresults(xl, yl)
def __sendresults(self, xl, yl):
""" send results to LavueController
:param xl: list of x's for each diffractogram
:type xl: :obj:`list` < :obj:`list` <float>>
:param yl: list of values for each diffractogram
:type yl: :obj:`list` < :obj:`list` <float>>
"""
results = {"tool": self.alias}
npl = len(xl)
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["nrlinecuts"] = len(xl)
for i in range(npl):
results["linecut_%s" % (i + 1)] = [xl[i], yl[i]]
results["unit"] = ["point", "x-pixel", "y-pixel"][self.__xindex]
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
@QtCore.pyqtSlot(int)
def _setCutsNumber(self, cid):
"""sets a number of cuts
:param cid: number of cuts
:type cid: :obj:`int`
"""
self.__ui.cutSpinBox.setValue(cid)
@QtCore.pyqtSlot()
def _message(self):
""" provides cut message
"""
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
ilabel = self._mainwidget.scalingLabel()
if self._mainwidget.currentCut() > -1:
crds = self._mainwidget.cutCoords()[
self._mainwidget.currentCut()]
crds = "[[%.2f, %.2f], [%.2f, %.2f], width=%.2f]" % tuple(crds)
else:
crds = "[[0, 0], [0, 0], width=0]"
if isinstance(intensity, np.ndarray) and \
intensity.size <= 3:
itn = [0 if (isinstance(it, float) and np.isnan(it))
else float(it) for it in intensity]
if len(itn) >= 3:
message = "%s, x = %.2f, y = %.2f, " \
"%s = (%.2f, %.2f, %.2f)" % (
crds, x, y, ilabel,
itn[0], itn[1], itn[2])
else:
message = "%s, x = %.2f, y = %.2f, %s = %.2f" % (
crds, x, y, ilabel, intensity)
self._mainwidget.setDisplayedText(message)
[docs]class ProjectionToolWidget(ToolBaseWidget):
""" Projections tool widget
"""
#: (:obj:`str`) tool name
name = "Projections"
#: (:obj:`str`) tool name alias
alias = "projections"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`Ui_ProjectionToolWidget')
#: ui_toolwidget object from qtdesigner
self.__ui = _projectionformclass()
self.__ui.setupUi(self)
#: (:class:`pyqtgraph.PlotDataItem`) 1D bottom plot
self.__bottomplot = None
#: (:class:`pyqtgraph.PlotDataItem`) 1D bottom plot
self.__rightplot = None
#: (:obj:`int`) function index
self.__funindex = 0
#: (:obj:`slice`) selected rows
self.__rows = None
#: (:obj:`slice`) selected columns
self.__columns = None
#: (:obj:`slice`) selected rows
self.__dsrows = None
#: (:obj:`slice`) selected columns
self.__dscolumns = None
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self.parameters.bottomplot = True
self.parameters.rightplot = True
self.parameters.vhbounds = True
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.funComboBox.currentIndexChanged,
self._setFunction],
[self.__ui.funComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self.__ui.rowsliceLineEdit.textChanged, self._updateRows],
[self.__ui.rowsliceLineEdit.textChanged, self._mainwidget.emitTCC],
[self.__ui.columnsliceLineEdit.textChanged, self._updateColumns],
[self.__ui.columnsliceLineEdit.textChanged,
self._mainwidget.emitTCC],
[self._mainwidget.scalesChanged, self._updateRows],
[self._mainwidget.scalesChanged, self._updateColumns],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "mapping" in cnf.keys():
idxs = ["mean", "sum"]
xcrd = str(cnf["mapping"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.funComboBox.setCurrentIndex(idx)
if "rows" in cnf.keys():
self.__ui.rowsliceLineEdit.setText(cnf["rows"])
if "columns" in cnf.keys():
self.__ui.columnsliceLineEdit.setText(cnf["columns"])
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["mapping"] = str(
self.__ui.funComboBox.currentText()).lower()
cnf["rows"] = self.__ui.rowsliceLineEdit.text()
cnf["columns"] = self.__ui.columnsliceLineEdit.text()
return json.dumps(cnf, cls=numpyEncoder)
def __updateslice(self, text, dx=None, ds=None):
""" create slices from the text
"""
rows = "ERROR"
dsrows = "ERROR"
if text:
try:
if ":" in text:
slices = text.split(":")
s0 = int(slices[0]) if slices[0].strip() else 0
s1 = int(slices[1]) if slices[1].strip() else None
if len(slices) > 2:
s2 = int(slices[2]) if slices[2].strip() else None
rows = slice(s0, s1, s2)
if dx is not None:
dsrows = slice((s0-dx)/ds, (s1-dx)/ds, s2/ds)
else:
dsrows = rows
else:
rows = slice(s0, s1)
if dx is not None:
dsrows = slice((s0-dx)/ds, (s1-dx)/ds)
else:
dsrows = rows
else:
rows = int(text)
if dx is not None:
dsrows = int((rows - dx)/ds)
else:
dsrows = rows
except Exception:
pass
else:
rows = None
dsrows = None
return rows, dsrows
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateSlices(self):
""" updates applied button"""
rtext = str(self.__ui.rowsliceLineEdit.text()).strip()
ctext = str(self.__ui.columnsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
self.__rows, self.__dsrows = self.__updateslice(
rtext, int(dy), int(ds2))
self.__columns, self.__dscolumns = self.__updateslice(
ctext, int(dx), int(ds1))
else:
self.__rows, self.__dsrows = self.__updateslice(rtext)
self.__columns, self.__dscolumns = self.__updateslice(ctext)
if self.__rows is None:
self._mainwidget.updateHBounds(None, None)
elif isinstance(self.__rows, int):
self._mainwidget.updateHBounds(self.__rows, self.__rows + 1)
elif isinstance(self.__rows, slice):
self._mainwidget.updateHBounds(self.__rows.start, self.__rows.stop)
if self.__columns is None:
self._mainwidget.updateVBounds(None, None)
elif isinstance(self.__columns, int):
self._mainwidget.updateVBounds(self.__columns, self.__columns + 1)
elif isinstance(self.__columns, slice):
self._mainwidget.updateVBounds(
self.__columns.start, self.__columns.stop)
self._plotCurves()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateRows(self):
""" updates applied button"""
rtext = str(self.__ui.rowsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
self.__rows, self.__dsrows = self.__updateslice(
rtext, int(dy), int(ds2))
else:
self.__rows, self.__dsrows = self.__updateslice(rtext)
if self.__rows is None:
self._mainwidget.updateHBounds(None, None)
elif isinstance(self.__rows, int):
self._mainwidget.updateHBounds(self.__rows, self.__rows + 1)
elif isinstance(self.__rows, slice):
self._mainwidget.updateHBounds(self.__rows.start, self.__rows.stop)
self._plotCurves()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateColumns(self):
""" updates applied button"""
text = str(self.__ui.columnsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if rwe:
self.__columns, self.__dscolumns = self.__updateslice(
text, int(dx), int(ds1))
else:
self.__columns, self.__dscolumns = self.__updateslice(text)
if self.__columns is None:
self._mainwidget.updateVBounds(None, None)
elif isinstance(self.__columns, int):
self._mainwidget.updateVBounds(self.__columns, self.__columns + 1)
elif isinstance(self.__columns, slice):
self._mainwidget.updateVBounds(
self.__columns.start, self.__columns.stop)
self._plotCurves()
@QtCore.pyqtSlot(int)
def _setFunction(self, findex):
""" set sum or mean function
:param findex: function index, i.e. 0:mean, 1:sum
:type findex: :obj:`int`
"""
self.__funindex = findex
self._plotCurves()
[docs] def activate(self):
""" activates tool widget
"""
if self.__bottomplot is None:
self.__bottomplot = self._mainwidget.onedbarbottomplot()
if self.__rightplot is None:
self.__rightplot = self._mainwidget.onedbarrightplot()
self.__bottomplot.show()
self.__rightplot.show()
self.__bottomplot.setVisible(True)
self.__rightplot.setVisible(True)
self._updateSlices()
self._plotCurves()
[docs] def deactivate(self):
""" activates tool widget
"""
if self.__bottomplot is not None:
self.__bottomplot.hide()
self.__bottomplot.setVisible(False)
self._mainwidget.removebottomplot(self.__bottomplot)
self.__bottomplot = None
if self.__rightplot is not None:
self.__rightplot.hide()
self.__rightplot.setVisible(False)
self._mainwidget.removerightplot(self.__rightplot)
self.__rightplot = None
@QtCore.pyqtSlot()
def _plotCurves(self):
""" plots the current image in 1d plots
"""
if self._mainwidget.currentTool() == self.name:
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None:
if self.__funindex:
npfun = np.nansum
else:
npfun = np.nanmean
if self.__dsrows == "ERROR":
sx = []
elif self.__dsrows is not None:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
if isinstance(self.__dsrows, slice):
sx = npfun(dts[:, self.__dsrows], axis=1)
else:
sx = dts[:, self.__dsrows]
except Exception:
sx = []
else:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
sx = npfun(dts, axis=1)
except Exception:
sx = []
if self.__dscolumns == "ERROR":
sy = []
if self.__dscolumns is not None:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
if isinstance(self.__dscolumns, slice):
sy = npfun(dts[self.__dscolumns, :], axis=0)
else:
sy = dts[self.__dscolumns, :]
except Exception:
sy = []
else:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
sy = npfun(dts, axis=0)
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
x, y, s1, s2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if self._mainwidget.transformations()[3]:
x, y = y, x
s1, s2 = s2, s1
xx = list(
range(int(x), len(sx) * int(s1) + int(x), int(s1)))
yy = list(
range(int(y), len(sy) * int(s2) + int(y), int(s2)))
else:
s1 = 1.0
s2 = 1.0
xx = list(range(len(sx)))
yy = list(range(len(sy)))
width = [s1] * len(sx)
height = [s2] * len(sy)
self.__bottomplot.setOpts(
y0=[0]*len(sx), y1=sx, x=xx,
width=width)
self.__bottomplot.drawPicture()
self.__rightplot.setOpts(
x0=[0]*len(sy), x1=sy, y=yy,
height=height)
self.__rightplot.drawPicture()
if self.__settings.sendresults:
xslice = self.__dsrows
yslice = self.__dscolumns
if hasattr(xslice, "start"):
xslice = [xslice.start, xslice.stop, xslice.step]
if hasattr(yslice, "start"):
yslice = [yslice.start, yslice.stop, yslice.step]
self.__sendresults(
xx,
[float(e) for e in sx],
s1, xslice,
yy,
[float(e) for e in sy],
s2, yslice,
"sum" if self.__funindex else "mean"
)
def __sendresults(self, xx, sx, xscale, xslice,
yy, sy, yscale, yslice, fun):
""" send results to LavueController
:param xx: x's coordinates
:type xx: :obj:`list` <float>
:param sx: projection to x coordinate
:type sx: :obj:`list` <float>
:param xscale: x scale
:type xscale: :obj:`float`
:param xslice: x slice
:type xslice: :obj:`list` <float>
:param yy: y's coordinates
:type yy: :obj:`list` <float>
:param sy: projection to y coordinate
:type sy: :obj:`list` <float>
:param yscale: y scale
:type yscale: :obj:`float`
:param yslice: y slice
:type yslice: :obj:`list` <float>
:param fun: projection function name
:type fun: :obj:`str`
"""
results = {"tool": self.alias}
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["xx"] = xx
results["sx"] = sx
results["xscale"] = xscale
results["xslice"] = xslice
results["yy"] = yy
results["sy"] = sy
results["yscale"] = yscale
results["yslice"] = yslice
results["function"] = fun
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
@QtCore.pyqtSlot()
def _message(self):
""" provides intensity message
"""
x, y, intensity = self._mainwidget.currentIntensity()[:3]
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
ilabel = self._mainwidget.scalingLabel()
message = "x = %i, y = %i, %s = %.2f" % (
x, y, ilabel, intensity)
self._mainwidget.setDisplayedText(message)
[docs]class OneDToolWidget(ToolBaseWidget):
""" 1d plot tool widget
"""
#: (:obj:`str`) tool name
name = "1d-Plot"
#: (:obj:`str`) tool name alias
alias = "1d-plot"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:class:`Ui_OneDToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _onedformclass()
self.__ui.setupUi(self)
#: (:obj:`list`<:class:`pyqtgraph.PlotDataItem`>) 1D plot
self.__curves = []
#: (:obj:`int`) current plot number
self.__nrplots = 0
#: ((:obj:`list`<:obj:`int`>) selected rows
self.__rows = [0]
#: ((:obj:`list`<:obj:`int`>) selected rows
self.__dsrows = [0]
#: ((:obj:`list`<:obj:`str`>) legend labels
self.__labels = []
#: ((:obj:`bool`) x in first row
self.__xinfirstrow = False
#: ((:obj:`bool`) accumalate status
self.__accumulate = False
#: ((:obj:`int`) buffer size
self.__buffersize = 1024
#: ((:class:`ndarray`) buffer
self.__buffer = None
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self.__ui.rowsLineEdit.setText("0")
self.parameters.bottomplot = True
self.parameters.infolineedit = ""
self.parameters.infotips = \
"coordinate info display for the mouse pointer"
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.rowsLineEdit.textChanged, self._updateRows],
[self.__ui.rowsLineEdit.textChanged, self._mainwidget.emitTCC],
[self._mainwidget.scalesChanged, self._updateRows],
[self.__ui.sizeLineEdit.textChanged, self._setBufferSize],
[self.__ui.sizeLineEdit.textChanged, self._mainwidget.emitTCC],
[self.__ui.xCheckBox.stateChanged, self._updateXRow],
[self.__ui.xCheckBox.stateChanged, self._mainwidget.emitTCC],
[self.__ui.accuPushButton.clicked, self._startStopAccu],
[self.__ui.resetPushButton.clicked, self._resetAccu],
[self.__ui.labelsPushButton.clicked, self._setLabels],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "rows_to_plot" in cnf.keys():
self.__ui.rowsLineEdit.setText(cnf["rows_to_plot"])
if "buffer_size" in cnf.keys():
self.__ui.sizeLineEdit.setText(str(cnf["buffer_size"]))
if "collect" in cnf.keys():
self._startStopAccu()
if "xrow" in cnf.keys():
self.__ui.xCheckBox.setChecked(bool(cnf["xrow"]))
if "reset" in cnf.keys():
if cnf["reset"]:
self._resetAccu()
if "labels" in cnf.keys():
self.__labels = cnf["labels"]
self.deactivate()
self.activate()
if "1d_stretch" in cnf.keys():
try:
val = int(cnf["1d_stretch"])
self._mainwidget.bottomplotStretch(val)
except Exception as e:
logger.warning(str(e))
# print(str(e))
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["rows_to_plot"] = self.__ui.rowsLineEdit.text()
try:
cnf["buffer_size"] = int(self.__ui.sizeLineEdit.text())
except Exception:
cnf["buffer_size"] = self.__ui.sizeLineEdit.text()
cnf["collect"] = self.__accumulate
cnf["xrow"] = self.__ui.xCheckBox.isChecked()
cnf["labels"] = self.__labels
return json.dumps(cnf, cls=numpyEncoder)
[docs] def beforeplot(self, array, rawarray):
""" command before plot
:param array: 2d image array
:type array: :class:`numpy.ndarray`
:param rawarray: 2d raw image array
:type rawarray: :class:`numpy.ndarray`
:return: 2d image array and raw image
:rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`)
"""
if self.__accumulate:
dts = rawarray
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
newrow = np.sum(dts[:, self.__dsrows], axis=1)
if self.__buffer is not None and \
self.__buffer.shape[1] == newrow.shape[0]:
if self.__buffer.shape[0] >= self.__buffersize:
self.__buffer = np.vstack(
[self.__buffer[
self.__buffer.shape[0] - self.__buffersize + 1:,
:],
newrow]
)
else:
self.__buffer = np.vstack([self.__buffer, newrow])
else:
self.__buffer = np.array([newrow])
return np.transpose(self.__buffer), rawarray
[docs] def activate(self):
""" activates tool widget
"""
self.__ui.sizeLineEdit.setText(str(self.__buffersize))
self._updateRows()
self._mainwidget.onedshowlegend(True)
[docs] def deactivate(self):
""" activates tool widget
"""
for cr in self.__curves:
cr.hide()
cr.setVisible(False)
self._mainwidget.removebottomplot(cr)
self.__curves = []
self.__nrplots = 0
self._mainwidget.onedshowlegend(False)
@QtCore.pyqtSlot()
def _setLabels(self):
""" launches label widget
:returns: apply status
:rtype: :obj:`bool`
"""
dform = edListDialog.EdListDialog(self)
dform.record = list(self.__labels or [])
dform.title = "Tango Detector Attributes"
dform.createGUI()
dform.exec_()
if dform.dirty:
self.__labels = dform.record
self.deactivate()
self.activate()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _resetAccu(self):
""" reset accumulation buffer
"""
self.__buffer = None
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _startStopAccu(self):
""" start/stop accumulation buffer
"""
if not self.__accumulate:
self.__accumulate = True
self.__ui.accuPushButton.setText("Stop")
else:
self.__accumulate = False
self.__ui.accuPushButton.setText("Collect")
self._mainwidget.emitTCC()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _setBufferSize(self):
""" start/stop accumulation buffer
"""
try:
self.__buffersize = int(self.__ui.sizeLineEdit.text())
except Exception as e:
# print(str(e))
logger.warning(str(e))
self.__buffersize = 1024
@QtCore.pyqtSlot(int)
def _updateXRow(self, value):
""" updates X row status
:param value: if True or not 0 x-cooridnates taken from the first row
:param value: :obj:`int` or :obj:`bool`
"""
self.__xinfirstrow = True if value else False
self._updateRows()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateRows(self):
""" updates applied button"""
text = str(self.__ui.rowsLineEdit.text()).strip()
rows = []
dsrows = []
rwe = None
if text:
if text == "ALL":
rows = [None]
else:
try:
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if self._mainwidget.transformations()[3]:
dx, dy = dy, dx
ds1, ds2 = ds2, ds1
stext = [rw for rw in re.split(",| ", text) if rw]
for rw in stext:
if ":" in rw:
slices = rw.split(":")
s0 = int(slices[0]) if slices[0].strip() else 0
s1 = int(slices[1]) if slices[1].strip() else 0
if len(slices) > 2:
s2 = int(slices[2]) if slices[2].strip() else 1
rows.extend(list(range(s0, s1, s2)))
if rwe:
dsrows.extend(
list(range(int((s0-dy)/ds2),
int((s1-dy)/ds2),
int(s2/ds2))))
else:
rows.extend(list(range(s0, s1)))
if rwe:
dsrows.extend(
list(range(int((s0-dy)/ds2),
int((s1-dy)/ds2))))
else:
rows.append(int(rw))
if rwe:
dsrows.append(int((int(rw)-dy)/ds2))
except Exception:
rows = []
self.__rows = rows
if not rwe:
self.__dsrows = rows
else:
self.__dsrows = dsrows
self._plotCurves()
@QtCore.pyqtSlot()
def _plotCurves(self):
""" plots the current image in 1d plots
"""
if self._mainwidget.currentTool() == self.name:
reset = False
if self.__settings.sendresults:
xl = []
yl = []
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None:
dtnrpts = dts.shape[1]
if self.__dsrows:
if self.__dsrows[0] is None:
if self.__xinfirstrow:
nrplots = dtnrpts - 1
else:
nrplots = dtnrpts
else:
nrplots = len(self.__dsrows)
else:
nrplots = 0
if self.__nrplots != nrplots:
while nrplots > len(self.__curves):
ii = len(self.__curves)
lb = str(ii + 1)
if self.__labels and len(self.__labels) > ii:
if self.__labels[ii] is not None:
lb = self.__labels[ii]
else:
lb = str(ii + 1)
self.__curves.append(
self._mainwidget.onedbottomplot(name=lb))
for i in range(nrplots):
self.__curves[i].show()
for i in range(nrplots, len(self.__curves)):
self.__curves[i].hide()
if nrplots < self.__nrplots:
reset = True
self.__nrplots = nrplots
if nrplots:
for i, cr in enumerate(self.__curves):
if i < nrplots:
cr.setPen(_pg.hsvColor(i/float(nrplots)))
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if self._mainwidget.transformations()[3]:
dx, dy = dy, dx
ds1, ds2 = ds2, ds1
for i in range(nrplots):
if self.__dsrows:
if self.__dsrows[0] is None:
if self.__xinfirstrow and i:
self.__curves[i].setData(
x=dts[:, 0], y=dts[:, i])
if self.__settings.sendresults:
xl.append([float(e) for e in dts[:, 0]])
yl.append([float(e) for e in dts[:, i]])
elif rwe:
y = dts[:, i]
x = np.linspace(
dx, len(y - 1) * ds1 + dx, len(y))
self.__curves[i].setData(x=x, y=y)
if self.__settings.sendresults:
xl.append([float(e) for e in x])
yl.append([float(e) for e in y])
else:
self.__curves[i].setData(dts[:, i])
if self.__settings.sendresults:
dt = dts[:, i]
xl.append(list(range(len(dt))))
yl.append([float(e) for e in dt])
self.__curves[i].setVisible(True)
elif (self.__dsrows[i] >= 0 and
self.__dsrows[i] < dtnrpts):
if self.__xinfirstrow:
self.__curves[i].setData(
x=dts[:, 0], y=dts[:, self.__dsrows[i]])
if self.__settings.sendresults:
xl.append([float(e) for e in dts[:, 0]])
yl.append([float(e) for e in
dts[:, self.__dsrows[i]]])
elif rwe:
y = dts[:, self.__dsrows[i]]
x = np.linspace(
dx, len(y - 1) * ds1 + dx, len(y))
self.__curves[i].setData(x=x, y=y)
if self.__settings.sendresults:
xl.append([float(e) for e in x])
yl.append([float(e) for e in y])
else:
self.__curves[i].setData(
dts[:, self.__dsrows[i]])
if self.__settings.sendresults:
dt = dts[:, self.__dsrows[i]]
xl.append(list(range(len(dt))))
yl.append([float(e) for e in dt])
self.__curves[i].setVisible(True)
else:
self.__curves[i].setVisible(False)
else:
self.__curves[i].setVisible(False)
if self.__settings.sendresults:
self.__sendresults(xl, yl)
else:
for cr in self.__curves:
cr.setVisible(False)
if reset:
self.deactivate()
self.activate()
def __sendresults(self, xl, yl):
""" send results to LavueController
:param xl: list of x's for each diffractogram
:type xl: :obj:`list` < :obj:`list` <float>>
:param yl: list of values for each diffractogram
:type yl: :obj:`list` < :obj:`list` <float>>
"""
results = {"tool": self.alias}
npl = len(xl)
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["nrplots"] = len(xl)
for i in range(npl):
results["onedplot_%s" % (i + 1)] = [xl[i], yl[i]]
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
@QtCore.pyqtSlot()
def _message(self):
""" provides intensity message
"""
x, y, intensity = self._mainwidget.currentIntensity()[:3]
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
ilabel = self._mainwidget.scalingLabel()
message = "x = %i, y = %i, %s = %.2f" % (
x, y, ilabel, intensity)
self._mainwidget.setDisplayedText(message)
[docs]class AngleQToolWidget(ToolBaseWidget):
""" angle/q tool widget
"""
#: (:obj:`str`) tool name
name = "Angle/Q"
#: (:obj:`str`) tool name alias
alias = "angle/q"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`int`) geometry space index -> 0: angle, 1 q-space
self.__gspaceindex = 0
#: (:obj:`int`) plot index -> 0: Cartesian, 1 polar-th, 2 polar-q
self.__plotindex = 0
#: (:class:`Ui_ROIToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _angleqformclass()
self.__ui.setupUi(self)
#: (:obj:`bool`) old lock value
self.__oldlocked = None
#: (:class:`numpy.array`) radial array cache
self.__lastradial = None
#: (:class:`numpy.array`) angle array cache
self.__lastangle = None
#: (:obj:`float`) energy cache
self.__lastenergy = None
#: (:obj:`float`) radmax cache
self.__lastradmax = None
#: (:obj:`float`) plotindex cache
self.__lastpindex = None
#: (:obj:`float`) detdistance cache
self.__lastdistance = None
#: (:obj:`float`) center x cache
self.__lastcenterx = None
#: (:obj:`float`) center y cache
self.__lastcentery = None
#: (:obj:`float`) pixelsizeycache
self.__lastpsizex = None
#: (:obj:`float`) pixelsizey cache
self.__lastpsizey = None
#: (:class:`numpy.array`) x array cache
self.__lastx = None
#: (:class:`numpy.array`) y array cache
self.__lasty = None
#: (:obj:`float`) maxdim cache
self.__lastmaxdim = None
#: (:obj:`float`) start position of radial q coordinate
self.__radqstart = None
#: (:obj:`float`) end position of radial q coordinate
self.__radqend = None
#: (:obj:`int`) grid size of radial q coordinate
self.__radqsize = None
#: (:obj:`float`) start position of radial theta coordinate
self.__radthstart = None
#: (:obj:`float`) end position of radial theta coordinate
self.__radthend = None
#: (:obj:`int`) grid size of radial theta coordinate
self.__radthsize = None
#: (:obj:`float`) start position of polar angle
self.__polstart = None
#: (:obj:`float`) end position of polar angle
self.__polend = None
#: (:obj:`int`) grid size of polar angle
self.__polsize = None
#: (:obj:`float`) range changed flag
self.__rangechanged = True
# self.parameters.lines = True
#: (:obj:`str`) infolineedit text
self.parameters.infolineedit = ""
self.parameters.infotips = ""
self.parameters.centerlines = True
self.parameters.toolscale = False
# self.parameters.rightplot = True
#: (`lavuelib.imageDisplayWidget.AxesParameters`) axes backup
self.__axes = None
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
#: (:obj:`float`) radial coordinate factor
self.__radmax = 1.
#: (:obj:`float`) polar coordinate factor
self.__polmax = 1.
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.angleqPushButton.clicked, self._setGeometry],
[self.__ui.rangePushButton.clicked, self._setPolarRange],
[self.__ui.angleqComboBox.currentIndexChanged,
self._setGSpaceIndex],
[self.__ui.angleqComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self.__ui.plotComboBox.currentIndexChanged,
self._setPlotIndex],
[self.__ui.plotComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self._mainwidget.mouseImageDoubleClicked,
self._updateCenter],
[self._mainwidget.geometryChanged, self.updateGeometryTip],
[self._mainwidget.geometryChanged, self._mainwidget.emitTCC],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "geometry" in cnf.keys():
try:
self._updateGeometry(cnf["geometry"])
except Exception as e:
# print(str(e))
logger.warning(str(e))
if "plot_type" in cnf.keys():
idxs = ["pixels", "polar-th", "polar-q"]
xcrd = str(cnf["plot_type"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.plotComboBox.setCurrentIndex(idx)
if "units" in cnf.keys():
idxs = ["angles", "q-space"]
xcrd = str(cnf["units"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.angleqComboBox.setCurrentIndex(idx)
if "plot_range" in cnf.keys():
try:
self._updatePolarRange(cnf["plot_range"])
except Exception as e:
# print(str(e))
logger.warning(str(e))
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["plot_type"] = str(
self.__ui.plotComboBox.currentText()).lower()
cnf["units"] = str(
self.__ui.angleqComboBox.currentText()).lower()
cnf["plot_range"] = [
[self.__polstart, self.__polend, self.__polsize],
[self.__radthstart, self.__radthend, self.__radthsize],
[self.__radqstart, self.__radqend, self.__radqsize]
]
cnf["geometry"] = {
"centerx": self.__settings.centerx,
"centery": self.__settings.centery,
"energy": self.__settings.energy,
"pixelsizex": self.__settings.pixelsizex,
"pixelsizey": self.__settings.pixelsizey,
"detdistance": self.__settings.detdistance,
}
return json.dumps(cnf, cls=numpyEncoder)
# @debugmethod
[docs] def activate(self):
""" activates tool widget
"""
self.__oldlocked = None
self.updateGeometryTip()
self.updateRangeTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
# @debugmethod
# @debugmethod
[docs] def beforeplot(self, array, rawarray):
""" command before plot
:param array: 2d image array
:type array: :class:`numpy.ndarray`
:param rawarray: 2d raw image array
:type rawarray: :class:`numpy.ndarray`
:return: 2d image array and raw image
:rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`)
"""
if self.__plotindex != 0:
if self._mainwidget.transformations()[0]:
tdata = self.__plotPolarImage(np.transpose(array))
else:
tdata = self.__plotPolarImage(array)
return tdata, tdata
def __havexychanged(self, radial, angle):
""" if xy changed
:param radial: radial coordinate
:type radial: :obj:`float` or :class:`numpy.array`
:param angle: polar angle coordinate
:type angle: :obj:`float` or :class:`numpy.array`
:returns: flag if (x, y) have changed
:rtype: :obj:`bool`
"""
recalc = False
if self.__lastradial is None or self.__lastangle is None or \
self.__lastenergy is None or self.__lastradmax is None or \
self.__lastpindex is None or self.__lastdistance is None or \
self.__lastcenterx is None or self.__lastcentery is None or \
self.__lastpsizex is None or self.__lastpsizey is None or \
self.__lastx is None or self.__lasty is None:
recalc = True
elif self.__lastenergy != self.__settings.energy:
recalc = True
elif self.__lastradmax != self.__radmax:
recalc = True
elif self.__lastpindex != self.__plotindex:
recalc = True
elif self.__lastdistance != self.__settings.detdistance:
recalc = True
elif self.__lastcenterx != self.__settings.centerx:
recalc = True
elif self.__lastcentery != self.__settings.centery:
recalc = True
elif self.__lastpsizex != self.__settings.pixelsizex:
recalc = True
elif self.__lastpsizey != self.__settings.pixelsizey:
recalc = True
elif (isinstance(radial, np.ndarray) and
not np.array_equal(self.__lastradial, radial)):
recalc = True
elif (not isinstance(radial, np.ndarray)
and self.__lastradial != radial):
recalc = True
elif (isinstance(angle, np.ndarray)
and not np.array_equal(self.__lastangle, angle)):
recalc = True
elif not isinstance(angle, np.ndarray) and self.__lastangle != angle:
recalc = True
return recalc
def __intintensity(self, radial, angle):
""" intensity interpolation function
:param radial: radial coordinate
:type radial: :obj:`float` or :class:`numpy.array`
:param angle: polar angle coordinate
:type angle: :obj:`float` or :class:`numpy.array`
:return: interpolated intensity
:rtype: :obj:`float` or :class:`numpy.array`
"""
if self.__rangechanged or self.__havexychanged(radial, angle):
if self.__plotindex == 1:
rstart = self.__radthstart \
if self.__radthstart is not None else 0
theta = radial * self.__radmax + rstart * math.pi / 180
else:
wavelength = 12398.4193 / self.__settings.energy
rstart = self.__radqstart \
if self.__radqstart is not None else 0
theta = 2. * np.arcsin(
(radial * self.__radmax + rstart) * wavelength
/ (4. * math.pi))
if self.__polsize is not None or \
self.__polstart is not None or self.__polend is not None:
pstart = self.__polstart if self.__polstart is not None else 0
angle = angle * self.__polmax + pstart
fac = 1000. * self.__settings.detdistance * np.tan(theta)
self.__lastx = self.__settings.centerx + \
fac * np.sin(angle * math.pi / 180) \
/ self.__settings.pixelsizex
self.__lasty = self.__settings.centery + \
fac * np.cos(angle * math.pi / 180) \
/ self.__settings.pixelsizey
self.__lastenergy = self.__settings.energy
self.__lastradmax = self.__radmax
self.__lastpindex = self.__plotindex
self.__lastdistance = self.__settings.detdistance
self.__lastcenterx = self.__settings.centerx
self.__lastcentery = self.__settings.centery
self.__lastpsizex = self.__settings.pixelsizex
self.__lastpsizey = self.__settings.pixelsizey
self.__lastradial = radial
self.__lastangle = angle
self.__rangechanged = False
if hasattr(self.__inter, "ev"):
return self.__inter.ev(self.__lastx, self.__lasty)
else:
return self.__inter(np.transpose(
[self.__lastx, self.__lasty], axes=[1, 2, 0]))
def __calculateRadMax(self, pindex, rdata=None):
""" recalculates radmax
:param rarray: 2d image array
:type rarray: :class:`numpy.ndarray`
"""
if rdata is None:
rdata = self._mainwidget.currentData()
if rdata is not None:
if pindex == 1:
if self.__lastmaxdim is not None \
and self.__radthsize is not None:
maxdim = self.__lastmaxdim
else:
maxdim = max(rdata.shape[0], rdata.shape[1])
rstart = self.__radthstart \
if self.__radthstart is not None else 0
if self.__radthend is None:
_, _, th0 = self.__pixel2theta(0, 0, False)
_, _, th1 = self.__pixel2theta(0, rdata.shape[1], False)
_, _, th2 = self.__pixel2theta(rdata.shape[0], 0, False)
_, _, th3 = self.__pixel2theta(
rdata.shape[0], rdata.shape[1], False)
try:
rmax = max(th0, th1, th2, th3)
except TypeError:
rmax = None
else:
rmax = (self.__radthend - rstart) * math.pi / 180.
if self.__radthsize is not None:
maxdim = self.__radthsize
if rmax is None:
# self._setGeometry()
logger.warning(
"Please set the detector geometry to continue")
return False
self.__radmax = rmax/float(maxdim)
elif pindex == 2:
if self.__lastmaxdim is not None \
and self.__radqsize is not None:
maxdim = self.__lastmaxdim
else:
maxdim = max(rdata.shape[0], rdata.shape[1])
rstart = self.__radqstart \
if self.__radqstart is not None else 0
if self.__radqend is None:
_, _, q0 = self.__pixel2q(0, 0, False)
_, _, q1 = self.__pixel2q(0, rdata.shape[1], False)
_, _, q2 = self.__pixel2q(rdata.shape[0], 0, False)
_, _, q3 = self.__pixel2q(
rdata.shape[0], rdata.shape[1], False)
try:
rmax = max(q0, q1, q2, q3)
except TypeError:
rmax = None
else:
rmax = (self.__radqend - rstart)
if self.__radqsize is not None:
maxdim = self.__radqsize
if rmax is None:
# self._setGeometry()
logger.warning(
"Please set the detector geometry to continue")
return False
self.__radmax = rmax/float(maxdim)
if pindex:
psize = self.__polsize if self.__polsize is not None else 360
pstart = self.__polstart if self.__polstart is not None else 0
pend = self.__polend if self.__polend is not None else 360
self.__polmax = float(pend - pstart) / psize
return True
# @debugmethod
def __plotPolarImage(self, rdata=None):
""" intensity interpolation function
:param rarray: 2d image array
:type rarray: :class:`numpy.ndarray`
:return: 2d image array
:rtype: :class:`numpy.ndarray`
"""
if self.__settings.energy > 0 and self.__settings.detdistance > 0 and \
self.__settings.pixelsizex > 0 and self.__settings.pixelsizey > 0:
if rdata is None:
rdata = self._mainwidget.currentData()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
xx = np.array(range(int(dx),
int((rdata.shape[0])*ds1 + dx),
int(ds1)))
yy = np.array(range(int(dy),
int((rdata.shape[1])*ds2 + dy),
int(ds2)))
else:
xx = np.array(range(rdata.shape[0]))
yy = np.array(range(rdata.shape[1]))
self.__inter = scipy.interpolate.RegularGridInterpolator(
(xx, yy), rdata,
fill_value=(0 if self._mainwidget.scaling() != 'log'
else -2),
bounds_error=False)
maxpolar = self.__polsize if self.__polsize is not None else 360
if self.__plotindex == 1:
if self.__radthsize is not None:
self.__lastmaxdim = self.__radthsize
else:
self.__lastmaxdim = max(rdata.shape[0], rdata.shape[1])
else:
if self.__radqsize is not None:
self.__lastmaxdim = self.__radqsize
else:
self.__lastmaxdim = max(rdata.shape[0], rdata.shape[1])
while not self.__calculateRadMax(self.__plotindex, rdata):
pass
tdata = np.fromfunction(
lambda x, y: self.__intintensity(x, y),
(int(self.__lastmaxdim), int(maxpolar)),
dtype=float)
# else:
# self.__inter = scipy.interpolate.RectBivariateSpline(
# xx, yy, rdata)
# tdata = np.fromfunction(
# lambda x, y: self.__intintensity(x, y),
# (int(self.__lastmaxdim), int(maxpolar)),
# dtype=float)
return tdata
# @debugmethod
@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`
"""
if self.__plotindex == 0:
txdata = None
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
xdata, ydata, useraxes=False)
if txdata is not None:
xdata = txdata
ydata = tydata
self.__settings.centerx = float(xdata)
self.__settings.centery = float(ydata)
self._mainwidget.writeAttribute("BeamCenterX", float(xdata))
self._mainwidget.writeAttribute("BeamCenterY", float(ydata))
self._message()
self.updateGeometryTip()
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _message(self):
""" provides geometry message
"""
message = ""
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
x, y, useraxes=False)
if txdata is not None:
x = txdata
y = tydata
ilabel = self._mainwidget.scalingLabel()
if self.__plotindex == 0:
if self.__gspaceindex == 0:
thetax, thetay, thetatotal = self.__pixel2theta(x, y)
if thetax is not None:
message = "th_x = %f deg, th_y = %f deg," \
" th_tot = %f deg, %s = %.2f" \
% (thetax * 180 / math.pi,
thetay * 180 / math.pi,
thetatotal * 180 / math.pi,
ilabel, intensity)
else:
qx, qy, q = self.__pixel2q(x, y)
if qx is not None:
message = u"q_x = %f 1/\u212B, q_y = %f 1/\u212B, " \
u"q = %f 1/\u212B, %s = %.2f" \
% (qx, qy, q, ilabel, intensity)
elif self.__plotindex == 1:
rstart = self.__radthstart \
if self.__radthstart is not None else 0
pstart = self.__polstart if self.__polstart is not None else 0
iscaling = self._mainwidget.scaling()
if iscaling != "linear" and not ilabel.startswith(iscaling):
if ilabel[0] == "(":
ilabel = "%s%s" % (iscaling, ilabel)
else:
ilabel = "%s(%s)" % (iscaling, ilabel)
message = u"th_tot = %f deg, polar = %f deg, " \
u" %s = %.2f" % (
x * 180 / math.pi * self.__radmax + rstart,
y * self.__polmax + pstart,
ilabel, intensity)
elif self.__plotindex == 2:
iscaling = self._mainwidget.scaling()
pstart = self.__polstart if self.__polstart is not None else 0
rstart = self.__radqstart \
if self.__radqstart is not None else 0
if iscaling != "linear" and not ilabel.startswith(iscaling):
if ilabel[0] == "(":
ilabel = "%s%s" % (iscaling, ilabel)
else:
ilabel = "%s(%s)" % (iscaling, ilabel)
message = u"q = %f 1/\u212B, polar = %f deg, " \
u" %s = %.2f" % (
x * self.__radmax + rstart,
y * self.__polmax + pstart,
ilabel, intensity)
self._mainwidget.setDisplayedText(message)
def __pixel2theta(self, xdata, ydata, xy=True):
""" converts coordinates from pixel positions to theta angles
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:param xy: flag
:type xy: :obj:`bool`
:returns: x-theta, y-theta, total-theta
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
thetax = None
thetay = None
thetatotal = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
xcentered = xdata - self.__settings.centerx
ycentered = ydata - self.__settings.centery
if xy:
thetax = math.atan(
xcentered * self.__settings.pixelsizex / 1000.
/ self.__settings.detdistance)
thetay = math.atan(
ycentered * self.__settings.pixelsizey / 1000.
/ self.__settings.detdistance)
r = math.sqrt(
(xcentered * self.__settings.pixelsizex / 1000.) ** 2
+ (ycentered * self.__settings.pixelsizey / 1000.) ** 2)
thetatotal = math.atan(
r / self.__settings.detdistance)
return thetax, thetay, thetatotal
def __pixel2q(self, xdata, ydata, xy=True):
""" converts coordinates from pixel positions to q-space coordinates
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:param xy: flag
:type xy: :obj:`bool`
:returns: q_x, q_y, q_total
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
qx = None
qy = None
q = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
thetax, thetay, thetatotal = self.__pixel2theta(
xdata, ydata, xy)
wavelength = 12398.4193 / self.__settings.energy
if xy:
qx = 4 * math.pi / wavelength * math.sin(thetax/2.)
qy = 4 * math.pi / wavelength * math.sin(thetay/2.)
q = 4 * math.pi / wavelength * math.sin(thetatotal/2.)
return qx, qy, q
def __tipmessage(self):
""" provides geometry messate
:returns: geometry text
:rtype: :obj:`unicode`
"""
return u"geometry:\n" \
u" center = (%s, %s) pixels\n" \
u" pixel_size = (%s, %s) \u00B5m\n" \
u" detector_distance = %s mm\n" \
u" energy = %s eV" % (
self.__settings.centerx,
self.__settings.centery,
self.__settings.pixelsizex,
self.__settings.pixelsizey,
self.__settings.detdistance,
self.__settings.energy
)
# @debugmethod
@QtCore.pyqtSlot()
def _setPolarRange(self):
""" launches range widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = rangeDialog.RangeDialog()
cnfdlg.polstart = self.__polstart
cnfdlg.polend = self.__polend
cnfdlg.polsize = self.__polsize
cnfdlg.radqstart = self.__radqstart
cnfdlg.radqend = self.__radqend
cnfdlg.radqsize = self.__radqsize
cnfdlg.radthstart = self.__radthstart
cnfdlg.radthend = self.__radthend
cnfdlg.radthsize = self.__radthsize
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__polstart = cnfdlg.polstart
self.__polend = cnfdlg.polend
self.__polsize = cnfdlg.polsize
self.__radthstart = cnfdlg.radthstart
self.__radthend = cnfdlg.radthend
self.__radthsize = cnfdlg.radthsize
self.__radqstart = cnfdlg.radqstart
self.__radqend = cnfdlg.radqend
self.__radqsize = cnfdlg.radqsize
self.__rangechanged = True
self.updateRangeTip()
self._setPlotIndex(self.__plotindex)
if self.__plotindex:
self._mainwidget.emitReplotImage()
# @debugmethod
def _updatePolarRange(self, plotrange):
""" update polar range
:returns: (start, end, size) for polar, theta and q coordinates
:rtype: :obj:`list`< [:obj:`float` ,:obj:`float` ,:obj:`float`] >
"""
try:
self.__polstart = float(plotrange[0][0])
except Exception:
self.__polstart = None
try:
self.__polend = float(plotrange[0][1])
except Exception:
self.__polend = None
try:
self.__polsize = float(plotrange[0][2])
except Exception:
self.__polsize = None
try:
self.__radthstart = float(plotrange[1][0])
except Exception:
self.__radthstart = None
try:
self.__radthend = float(plotrange[1][1])
except Exception:
self.__radthend = None
try:
self.__radthsize = float(plotrange[1][2])
except Exception:
self.__radthsize = None
try:
self.__radqstart = float(plotrange[2][0])
except Exception:
self.__radqstart = None
try:
self.__radqend = float(plotrange[2][1])
except Exception:
self.__radqend = None
try:
self.__radqsize = float(plotrange[2][2])
except Exception:
self.__radqsize = None
self.__rangechanged = True
self.updateRangeTip()
# self._setPlotIndex(self.__plotindex)
if self.__plotindex:
self._mainwidget.emitReplotImage()
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _updateGeometry(self, geometry):
""" update geometry widget
:param geometry: geometry dictionary
:type geometry: :obj:`dict` < :obj:`str`, :obj:`list`>
"""
try:
if "centerx" in geometry.keys():
self.__settings.centerx = float(geometry["centerx"])
self._mainwidget.writeAttribute(
"BeamCenterX", float(self.__settings.centerx))
except Exception:
pass
try:
if "centery" in geometry.keys():
self.__settings.centery = float(geometry["centery"])
self._mainwidget.writeAttribute(
"BeamCenterY", float(self.__settings.centery))
except Exception:
pass
try:
if "energy" in geometry.keys():
self.__settings.energy = float(geometry["energy"])
self._mainwidget.writeAttribute(
"Energy", float(self.__settings.energy))
except Exception:
pass
try:
if "pixelsizex" in geometry.keys():
self.__settings.pixelsizex = float(geometry["pixelsizex"])
except Exception:
pass
try:
if "pixelsizey" in geometry.keys():
self.__settings.pixelsizey = float(geometry["pixelsizey"])
except Exception:
pass
try:
if "detdistance" in geometry.keys():
self.__settings.detdistance = float(geometry["detdistance"])
self._mainwidget.writeAttribute(
"DetectorDistance",
float(self.__settings.detdistance))
except Exception:
pass
if geometry:
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
if self.__plotindex:
self._mainwidget.emitReplotImage()
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _setGeometry(self):
""" launches geometry widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = geometryDialog.GeometryDialog()
cnfdlg.centerx = self.__settings.centerx
cnfdlg.centery = self.__settings.centery
cnfdlg.energy = self.__settings.energy
cnfdlg.pixelsizex = self.__settings.pixelsizex
cnfdlg.pixelsizey = self.__settings.pixelsizey
cnfdlg.detdistance = self.__settings.detdistance
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__settings.centerx = cnfdlg.centerx
self.__settings.centery = cnfdlg.centery
self.__settings.energy = cnfdlg.energy
self.__settings.pixelsizex = cnfdlg.pixelsizex
self.__settings.pixelsizey = cnfdlg.pixelsizey
self.__settings.detdistance = cnfdlg.detdistance
self._mainwidget.writeAttribute(
"BeamCenterX", float(self.__settings.centerx))
self._mainwidget.writeAttribute(
"BeamCenterY", float(self.__settings.centery))
self._mainwidget.writeAttribute(
"Energy", float(self.__settings.energy))
self._mainwidget.writeAttribute(
"DetectorDistance",
float(self.__settings.detdistance))
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
if self.__plotindex:
self._mainwidget.emitReplotImage()
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot(int)
def _setGSpaceIndex(self, gindex):
""" set gspace index
:param gspace: g-space index, i.e. angle or q-space
:type gspace: :obj:`int`
"""
self.__gspaceindex = gindex
# @debugmethod
@QtCore.pyqtSlot(int)
def _setPlotIndex(self, pindex=None):
""" set gspace index
:param gspace: g-space index,
: i.e. 0: Cartesian, 1: polar-th, 2: polar-q
:type gspace: :obj:`int`
"""
if pindex:
if not self.__calculateRadMax(pindex):
self.__ui.plotComboBox.setCurrentIndex(0)
return
self.parameters.centerlines = False
self.parameters.toolscale = True
if pindex == 1:
rscale = 180. / math.pi * self.__radmax
rstart = self.__radthstart \
if self.__radthstart is not None else 0
else:
rscale = self.__radmax
rstart = self.__radqstart \
if self.__radqstart is not None else 0
pstart = self.__polstart if self.__polstart is not None else 0
pscale = self.__polmax
self._mainwidget.setToolScale([rstart, pstart], [rscale, pscale])
if not self.__plotindex:
self.__oldlocked = self._mainwidget.setAspectLocked(False)
else:
if self.__oldlocked is not None:
self._mainwidget.setAspectLocked(self.__oldlocked)
self.parameters.centerlines = True
self.parameters.toolscale = False
if pindex is not None:
self.__plotindex = pindex
if self.__ui.plotComboBox.currentIndex != pindex:
self.__ui.plotComboBox.setCurrentIndex(pindex)
self._mainwidget.updateinfowidgets(self.parameters)
self._mainwidget.emitReplotImage()
[docs] @QtCore.pyqtSlot()
def updateGeometryTip(self):
""" update geometry tips
"""
message = self.__tipmessage()
self._mainwidget.updateDisplayedTextTip(
"coordinate info display for the mouse pointer\n%s"
% message)
self.__ui.angleqPushButton.setToolTip(
"Input physical parameters\n%s" % message)
self.__ui.angleqComboBox.setToolTip(
"Select the display space\n%s" % message)
self.__ui.toolLabel.setToolTip(
"coordinate info display for the mouse pointer\n%s" % message)
[docs] @QtCore.pyqtSlot()
def updateRangeTip(self):
""" update geometry tips
"""
self.__ui.rangePushButton.setToolTip(
u"Polar: [%s, %s] deg, size=%s\n"
u"th_tot: [%s, %s] deg, size=%s\n"
u"q: [%s, %s] 1/\u212B, size=%s" % (
self.__polstart if self.__polstart is not None else "0",
self.__polend if self.__polend is not None else "360",
self.__polsize if self.__polsize is not None else "max",
self.__radthstart if self.__radthstart is not None else "0",
self.__radthend if self.__radthend is not None else "thmax",
self.__radthsize if self.__radthsize is not None else "max",
self.__radqstart if self.__radqstart is not None else "0",
self.__radqend if self.__radqend is not None else "qmax",
self.__radqsize if self.__radqsize is not None else "max")
)
[docs]class DiffractogramToolWidget(ToolBaseWidget):
""" diffractogram tool widget
"""
#: (:obj:`str`) tool name
name = "Diffractogram"
#: (:obj:`str`) tool name alias
alias = "diffractogram"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ("PYFAI",)
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`int`) unit index
# -> 0: q_nm^-1, 1: q_A-1, 2: 2th_deg, 3: 2th_rad
self.__unitindex = 0
#: (:obj:`list` <:obj:`str`>) list of units
self.__units = ["q_nm^-1", "q_A^-1", "2th_deg",
"2th_rad", "r_mm", "r_pixel"]
#: (:obj:`int`) plot index
# -> 0: Image, <i>: Buffer <i>
self.__plotindex = 0
#: (:obj:`bool`) old lock value
self.__oldlocked = None
#: (:class:`Ui_ROIToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _diffractogramformclass()
self.__ui.setupUi(self)
#: (:obj:`bool`) old lock value
self.__oldlocked = None
#: (:obj:`bool`) reset scale flag
self.__resetscale = True
#: ((:obj:`bool`) show diffractogram status
self.__showdiff = False
#: (:obj:`list` < [:obj:`float`, :obj:`float`] >)
# range positions of radial in current units
self.__radrange = [None]
#: (:obj:`list` < :obj:`float`>) start position of radial in deg
self.__radstart = [None]
#: (:obj:`list` < :obj:`float >) end position of radial in deg
self.__radend = [None]
#: (:obj:`list` < :obj:`float`>) start position of azimuth angle in deg
self.__azstart = [None]
#: (:obj:`list` < :obj:`float`>) end position of azimuth angle in deg
self.__azend = [None]
#: (:obj:`list` < [:obj:`float`, :obj:`float`] > )
# range positions of azimuth in deg
self.__azrange = [None]
#: (:obj:`list`<:class:`pyqtgraph.PlotDataItem`>) 1D plot freezed
self.__freezed = []
#: (:obj:`list`<:class:`pyqtgraph.PlotDataItem`>) 1D plot
self.__curves = []
#: (:obj:`int`) current plot number
self.__nrplots = 0
#: (:obj:`bool`) progressbar is running
self.__progressFlag = False
#: (:class:`pyqtgraph.QtWidgets.QProgressDialog`) progress bar
self.__progress = None
#: (:obj:`list` <:class:`lavuelib.commandThread.CommandThread`>) \
#: command thread
self.__commandthread = None
#: ( :obj:`list` < :obj:`list` < :obj:`list` < (float, float) > > >)
# list of region lines
self.__regions = []
#: (: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:`bool`) accumalate status
self.__accumulate = False
#: (:obj:`bool`) show buffer status
self.__showbuffer = False
#: (:obj:`int`) buffer size
self.__buffersize = 1024
#: ([:class:`ndarray`,:class:`ndarray` :class:`ndarray`,
# :class:`ndarray`]) y-buffers for diffractogram
self.__buffers = [None, None, None, None]
#: (:obj:`list` < [:obj:`int`, :obj:`int`] > )
# x-buffers of (position, scale) for diffractogram
self.__xbuffers = [None, None, None, None]
#: (:obj:`list` < `list` < :obj:`int` > > ) time stamps
self.__timestamps = [[], [], [], []]
# self.parameters.lines = True
#: (:obj:`str`) infolineedit text
self.parameters.infolineedit = ""
self.parameters.infotips = ""
self.parameters.bottomplot = True
self.parameters.centerlines = True
self.parameters.toolscale = False
self.parameters.regions = True
# self.parameters.rightplot = True
#: (`lavuelib.imageDisplayWidget.AxesParameters`) axes backup
self.__axes = None
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self.setColors(self.__settings.roiscolors)
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.diffSpinBox.valueChanged, self._updateDiffNumber],
[self.__ui.diffSpinBox.valueChanged, self._mainwidget.emitTCC],
[self.__ui.rangePushButton.clicked, self._setPolarRange],
[self.__ui.showPushButton.clicked, self._showhideDiff],
[self.__ui.nextPushButton.clicked, self._nextPlotDiff],
[self.__ui.calibrationPushButton.clicked, self._loadCalibration],
[self.__ui.unitComboBox.currentIndexChanged,
self._setUnitIndex],
[self.__ui.unitComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self.__ui.mainplotComboBox.currentIndexChanged,
self._setPlotIndex],
[self.__ui.mainplotComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self._mainwidget.mouseImageDoubleClicked,
self._updateCenter],
[self.__ui.sizeLineEdit.textChanged, self._setBufferSize],
[self.__ui.sizeLineEdit.textChanged, self._mainwidget.emitTCC],
[self.__ui.accuPushButton.clicked, self._startStopAccu],
[self.__ui.bufferPushButton.clicked, self._showBuffer],
[self.__ui.resetPushButton.clicked, self._resetAccu],
[self._mainwidget.geometryChanged, self.updateGeometryTip],
[self._mainwidget.geometryChanged, self._mainwidget.emitTCC],
[self._mainwidget.geometryChanged, self._updateSetCenter],
[self._mainwidget.freezeBottomPlotClicked, self._freezeplot],
[self._mainwidget.clearBottomPlotClicked, self._clearplot],
[self._mainwidget.mouseImagePositionChanged, self._message],
[self._mainwidget.colorsChanged, self.setColors]
]
# self.__ui.showPushButton.hide()
# self.__ui.nextPushButton.hide()
self._showBuffer(False)
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "calibration" in cnf.keys():
calib = cnf["calibration"]
try:
self._loadCalibration(calib)
except Exception as e:
logger.warning(str(e))
if "diff_number" in cnf.keys():
try:
self.__ui.diffSpinBox.setValue(int(cnf["diff_number"]))
except Exception as e:
logger.warning(str(e))
# print(str(e))
if "diff_ranges" in cnf.keys():
try:
self._updatePolarRange(cnf["diff_ranges"])
except Exception as e:
# print(str(e))
logger.warning(str(e))
if "diff_units" in cnf.keys():
idxs = ["q [1/nm]", "q [1/A]", "2th [deg]", "2th [rad]",
"r [mm]", "r [pixel]"]
xcrd = str(cnf["diff_units"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.unitComboBox.setCurrentIndex(idx)
if "show_diff" in cnf.keys():
if cnf["show_diff"]:
if str(self.__ui.showPushButton.text()) == "Show":
self._showhideDiff()
else:
if str(self.__ui.showPushButton.text()) == "Stop":
self._showhideDiff()
if "stop_diff" in cnf.keys():
if cnf["stop_diff"]:
if str(self.__ui.showPushButton.text()) == "Stop":
self._showhideDiff()
if "next" in cnf.keys():
if cnf["next"]:
if str(self.__ui.nextPushButton.text()) == "Next":
self._nextPlotDiff()
if "main_plot" in cnf.keys():
idxs = [
"image", "buffer 1", "buffer 2", "buffer 3", "buffer 4"]
xcrd = str(cnf["main_plot"]).lower()
try:
idx = idxs.index(xcrd)
except Exception as e:
logger.warning(str(e))
# print(str(e))
idx = 0
self.__ui.mainplotComboBox.setCurrentIndex(idx)
if "buffering" in cnf.keys():
self._showBuffer()
if "buffer_size" in cnf.keys():
self.__ui.sizeLineEdit.setText(str(cnf["buffer_size"]))
if "reset" in cnf.keys():
if cnf["reset"]:
self._resetAccu()
if "collect" in cnf.keys():
if cnf["collect"] and not self.__accumulate:
self._startStopAccu()
elif not cnf["collect"] and self.__accumulate:
self._startStopAccu()
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["calibration"] = self.__settings.calibrationfilename
cnf["diff_number"] = self.__ui.diffSpinBox.value()
ranges = []
for nip in range(cnf["diff_number"]):
azs = self.__azstart[nip] if len(self.__azstart) > nip else None
aze = self.__azend[nip] if len(self.__azend) > nip else None
rds = self.__radstart[nip] if len(self.__radstart) > nip else None
rde = self.__radend[nip] if len(self.__radend) > nip else None
ranges.append([azs, aze, rds, rde])
cnf["diff_ranges"] = ranges
units = ["q [1/nm]", "q [1/A]", "2th [deg]", "2th [rad]",
"r [mm]", "r [pixel]"]
idx = self.__ui.unitComboBox.currentIndex()
cnf["diff_units"] = units[idx]
try:
cnf["buffer_size"] = int(self.__ui.sizeLineEdit.text())
except Exception:
cnf["buffer_size"] = self.__ui.sizeLineEdit.text()
cnf["buffering"] = self.__showbuffer
cnf["collect"] = self.__accumulate
cnf["show_diff"] = self.__showdiff
cnf["main_plot"] = str(
self.__ui.mainplotComboBox.currentText()).lower()
return json.dumps(cnf, cls=numpyEncoder)
# @debugmethod
@QtCore.pyqtSlot()
def _setBufferSize(self):
""" start/stop accumulation buffer
"""
try:
self.__buffersize = int(self.__ui.sizeLineEdit.text())
self._resetAccu()
except Exception as e:
# print(str(e))
logger.warning(str(e))
self.__buffersize = 1024
# @debugmethod
@QtCore.pyqtSlot()
def _resetAccu(self):
""" reset accumulation buffer
"""
self.__buffers = [None, None, None, None]
self.__xbuffers = [None, None, None, None]
self.__timestamps = [[], [], [], []]
# np.empty(shape=(int(self.__settings.diffnpt), 0))
# diffdata = None
self.__resetscale = True
if self.__plotindex != 0:
diffdata = np.zeros(shape=(1, 1))
self._mainwidget.updateImage(diffdata, diffdata)
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _startStopAccu(self):
""" start/stop accumulation buffer
"""
if not self.__accumulate:
self.__accumulate = True
self.__ui.accuPushButton.setText("Stop")
else:
self.__accumulate = False
self.__ui.accuPushButton.setText("Collect")
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _showBuffer(self, status=None):
""" show/hide buffer widgets
"""
if status is not None:
self.__showbuffer = not status
if not self.__showbuffer:
self.__showbuffer = True
if QtGui.QIcon.hasThemeIcon("go-up"):
icon = QtGui.QIcon.fromTheme("go-up")
self.__ui.bufferPushButton.setIcon(icon)
else:
# self.__ui.bufferPushButton.setText(u" \u25B2 Buffering")
self.__ui.bufferPushButton.setText(u" \u25B4 Buffering ")
self.__ui.bufferFrame.show()
else:
self.__showbuffer = False
if QtGui.QIcon.hasThemeIcon("go-down"):
icon = QtGui.QIcon.fromTheme("go-down")
self.__ui.bufferPushButton.setIcon(icon)
else:
# self.__ui.bufferPushButton.setText(u" \u25BC Buffering")
self.__ui.bufferPushButton.setText(u" \u25BE Buffering ")
self.__ui.bufferFrame.hide()
self.adjustSize()
self._mainwidget.emitTCC()
# @debugmethod
[docs] @QtCore.pyqtSlot(str)
def setColors(self, colors=None, force=False):
""" sets colors
:param colors: json list of roi colors
:type colors: :obj:`str`
:returns: change status
:rtype: :obj:`bool`
"""
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 i, cr in enumerate(self.__curves):
clr = tuple(colors[i % len(colors)]) if colors \
else self.__defpen
cr.setPen(_pg.mkPen(clr))
# @debugmethod
[docs] def runProgress(self, commands, onclose="_closeReset",
text="Updating diffractogram ranges ..."):
""" starts progress thread with the given commands
:param commands: list of commands
:type commands: :obj:`list` <:obj:`str`>
:param onclose: close command name
:type onclose: :obj:`str`
:param text: text to display
:type text: :obj:`str`
"""
if self.__progress:
return
if self.__commandthread:
self.__commandthread.setParent(None)
self.__commandthread = None
self.__commandthread = commandThread.CommandThread(
self, commands, self._mainwidget)
oncloseaction = getattr(self, onclose)
self.__commandthread.finished.connect(
oncloseaction, QtCore.Qt.QueuedConnection)
self.__progress = None
self.__progress = QtWidgets.QProgressDialog(
text, "Cancel", 0, 0, self)
self.__progress.setWindowModality(QtCore.Qt.WindowModal)
self.__progress.setCancelButton(None)
self.__progress.rejected.connect(
self.waitForThread, QtCore.Qt.QueuedConnection)
self.__commandthread.start()
self.__progress.show()
# @debugmethod
[docs] def waitForThread(self):
""" waits for running thread
"""
logger.debug("waiting for Thread")
if self.__commandthread:
try:
self.__commandthread.wait()
except Exception as e:
logger.warning(str(e))
logger.debug("waiting for Thread ENDED")
# @debugmethod
@QtCore.pyqtSlot()
def _freezeplot(self):
""" freeze plot
"""
self._clearplot()
nrplots = self.__nrplots
while nrplots > len(self.__freezed):
cr = self._mainwidget.onedbottomplot()
cr.setPen(_pg.mkColor(0.5))
self.__freezed.append(cr)
for i in range(nrplots):
dt = self.__curves[i].xData, self.__curves[i].yData
self.__freezed[i].setData(*dt)
self.__freezed[i].show()
self.__freezed[i].setVisible(True)
for i in range(nrplots, len(self.__freezed)):
self.__freezed[i].hide()
self.__freezed[i].setVisible(True)
# print(type(cr))
# @debugmethod
@QtCore.pyqtSlot()
def _clearplot(self):
""" clear freezed plot
"""
for cr in self.__freezed:
cr.setVisible(False)
# @debugmethod
@QtCore.pyqtSlot(int)
def _updateDiffNumber(self, did):
""" update diffractorgram number
:param did: diffractogram id
:type did: :obj:`int`
"""
QtCore.QCoreApplication.processEvents()
self.updateRangeTip()
self.__updateBufferCombobox(did)
# self.__nrplots = self.__ui.diffSpinBox.value()
#
self._plotDiff()
self.setColors(self.__settings.roiscolors, True)
# self.__updateregion()
QtCore.QCoreApplication.processEvents()
while len(self.__regions) > self.__nrplots:
self.regions.pop()
QtCore.QCoreApplication.processEvents()
self._updateRegionsAndPlot()
# @debugmethod
def __updateBufferCombobox(self, did):
""" update buffer combobox
:param did: diffractogram id
:type did: :obj:`int`
"""
combo = self.__ui.mainplotComboBox
# idx = combo.currentIndex()
cnt = combo.count()
while did >= cnt:
if cnt:
combo.addItem("Buffer %s" % (cnt))
else:
combo.addItem("Image")
cnt = combo.count()
while did + 1 < cnt:
combo.removeItem(cnt - 1)
cnt = combo.count()
# if idx >= cnt:
# changed = True
# @debugmethod
# @debugmethod
[docs] def activate(self):
""" activates tool widget
"""
self.__oldlocked = None
self.__ui.sizeLineEdit.setText(str(self.__buffersize))
self.updateGeometryTip()
self.updateRangeTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
self.__updateButtons()
if not self.__curves:
self.__curves.append(self._mainwidget.onedbottomplot(True))
self.__nrplots = 1
for curve in self.__curves:
curve.show()
curve.setVisible(True)
self.setColors(self.__settings.roiscolors, True)
# if self.__settings.calibrationfilename:
# self._loadCalibration(
# self.__settings.calibrationfilename)
# self.__ui.diffSpinBox.setEnabled(False)
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
if aistat:
self._plotDiff()
self._mainwidget.bottomplotShowMenu(True, True)
self.__ui.mainplotComboBox.setCurrentIndex(0)
self._setPlotIndex(0)
# @debugmethod
@QtCore.pyqtSlot()
def _nextPlotDiff(self):
""" plot all diffractograms and update
"""
self._plotDiffWithBuffering()
if self.__plotindex > 0 and \
self.__plotindex <= len(self.__buffers):
if self.__buffers[self.__plotindex - 1] is not None:
diffdata = np.transpose(self.__buffers[self.__plotindex - 1])
else:
diffdata = None
diffdata = np.zeros(shape=(1, 1))
self.__resetscale = True
# np.empty(shape=(int(self.__settings.diffnpt), 0))
self._mainwidget.updateImage(diffdata, diffdata)
self._mainwidget.emitTCC()
# @debugmethod
[docs] def deactivate(self):
""" deactivates tool widget
"""
self.waitForThread()
self._mainwidget.bottomplotShowMenu()
for curve in self.__curves:
curve.hide()
curve.setVisible(False)
self._mainwidget.removebottomplot(curve)
self.__curves = []
for freezed in self.__freezed:
freezed.hide()
freezed.setVisible(False)
self._mainwidget.removebottomplot(freezed)
self.__freezed = []
# @debugmethod
def _plotDiffWithBuffering(self):
""" plot diffractogram with buffering
"""
xl, yl, ts = self._plotDiff()
# print(yl)
if self.__accumulate:
for i, yy in enumerate(yl):
newrow = np.array(yy)
if self.__buffers[i] is not None and \
self.__buffers[i].shape[1] == newrow.shape[0]:
if self.__buffers[i].shape[0] >= self.__buffersize:
self.__buffers[i] = np.vstack(
[self.__buffers[i][
self.__buffers[i].shape[0]
- self.__buffersize + 1:,
:],
newrow]
)
if self.__timestamps[i]:
self.__timestamps[i].pop(0)
else:
self.__buffers[i] = np.vstack(
[self.__buffers[i], newrow])
self.__timestamps[i].append(ts)
else:
self.__buffers[i] = np.array([yy])
self.__timestamps[i] = [ts]
if self.__xbuffers[i] is None or self.__resetscale:
xbuf = np.array(xl[i])
pos = 0.0
sc = 1.0
if len(xbuf) > 0:
pos = xbuf[0]
if len(xbuf) > 1:
sc = (xbuf[-1] - xbuf[0])/(len(xbuf) - 1)
self.__xbuffers[i] = [pos, sc]
if (self.__ui.mainplotComboBox.currentIndex() -
1 == i):
self._mainwidget.setToolScale(
[pos, 0], [sc, 1])
# self._mainwidget.setToolScale(
# [0, 0], [1, 1])
self.__resetscale = False
# @debugmethod
[docs] def beforeplot(self, array, rawarray):
""" command before plot
:param array: 2d image array
:type array: :class:`numpy.ndarray`
:param rawarray: 2d raw image array
:type rawarray: :class:`numpy.ndarray`
:return: 2d image array and raw image
:rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`)
"""
if self.__showdiff:
self._plotDiffWithBuffering()
if self.__plotindex > 0 and \
self.__plotindex <= len(self.__buffers):
if self.__buffers[self.__plotindex - 1] is not None:
diffdata = np.transpose(self.__buffers[self.__plotindex - 1])
else:
diffdata = None
diffdata = np.zeros(shape=(1, 1))
self.__resetscale = True
# np.empty(shape=(int(self.__settings.diffnpt), 0))
return (diffdata, diffdata)
# @debugmethod
@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`
"""
if self.__plotindex == 0:
txdata = None
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
xdata, ydata, useraxes=False)
if txdata is not None:
xdata = txdata
ydata = tydata
self.__settings.centerx = float(xdata)
self.__settings.centery = float(ydata)
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
aif = self.__settings.ai.getFit2D()
self.__settings.ai.setFit2D(aif["directDist"],
self.__settings.centerx,
self.__settings.centery,
aif["tilt"],
aif["tiltPlanRotation"])
self._mainwidget.writeAttribute("BeamCenterX", float(xdata))
self._mainwidget.writeAttribute("BeamCenterY", float(ydata))
self._message()
self.__updateregion()
self._plotDiff()
self.updateGeometryTip()
self._resetAccu()
self._mainwidget.emitTCC()
# self.__resetscale = True
# @debugmethod
@QtCore.pyqtSlot()
def _updateSetCenter(self):
""" updates the image center
"""
# return
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
aif = self.__settings.ai.getFit2D()
self.__settings.ai.setFit2D(aif["directDist"],
self.__settings.centerx,
self.__settings.centery,
aif["tilt"],
aif["tiltPlanRotation"])
self._resetAccu()
self.__updateregion()
self._plotDiff()
# self.__resetscale = True
# @debugmethod
@QtCore.pyqtSlot()
def _loadCalibration(self, fileName=None):
""" load calibration file
"""
if fileName is None:
fileDialog = QtWidgets.QFileDialog()
fileout = fileDialog.getOpenFileName(
self._mainwidget, 'Open calibration file',
self.__settings.calibrationfilename or '/ramdisk/',
"PONI (*.poni);;All files (*)"
)
if isinstance(fileout, tuple):
fileName = str(fileout[0])
else:
fileName = str(fileout)
if fileName:
try:
with QtCore.QMutexLocker(self.__settings.aimutex):
self.__settings.ai = pyFAI.load(fileName)
# self.__settings.ai.rot1 = 0
# self.__settings.ai.rot1 = math.pi/4 * 0.5
# self.__settings.ai.rot1 = math.pi/4 * 0.75
# self.__settings.ai.rot1 = math.pi/4.
# self.__settings.ai.rot2 = math.pi/4.
# self.__settings.ai.rot3 = math.pi/2.
# print(str(self.__settings.ai))
self.__settings.calibrationfilename = fileName
self.__writedetsettings()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
except Exception as e:
# print(str(e))
logger.warning(str(e))
with QtCore.QMutexLocker(self.__settings.aimutex):
self.__settings.ai = None
with QtCore.QMutexLocker(self.__settings.aimutex):
self.__updateButtons(self.__settings.ai is not None)
self.__updateregion()
self.updateGeometryTip()
self._resetAccu()
self._mainwidget.emitTCC()
self.__resetscale = True
# @debugmethod
def __writedetsettings(self):
""" write detector settings from ai object
"""
self.__settings.updateDetectorParameters()
self._mainwidget.writeDetectorAttributes()
# @debugmethod
def __updateButtons(self, status=None):
""" update buttons
:param status: show button flag
:type status: :obj:`bool`
"""
if status is None:
status = self.__settings.ai is not None
self.__ui.showPushButton.setEnabled(status)
self.__ui.nextPushButton.setEnabled(status)
self.__ui.diffSpinBox.setEnabled(status)
# @debugmethod
@QtCore.pyqtSlot()
def _message(self):
""" provides geometry message
"""
message = ""
if self.__plotindex == 0:
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
x, y, useraxes=False)
if txdata is not None:
x = txdata
y = tydata
ilabel = self._mainwidget.scalingLabel()
chi = None
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
chi = self.__settings.ai.chi(
np.array([y - 0.5]), np.array([x - 0.5]))
if len(chi):
chi = chi[0]
if self.__unitindex in [2, 3]:
tth = None
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
tth = self.__settings.ai.tth(
np.array([y - 0.5]), np.array([x - 0.5])),
if len(tth):
tth = tth[0]
if tth is not None and chi is not None:
unit = "rad"
if self.__unitindex == 2:
unit = "deg"
chi = chi * 180./math.pi
tth = tth * 180./math.pi
message = "x, y = [%s, %s], tth = %f %s, chi = %f %s," \
" %s = %.2f" \
% (x, y, tth, unit,
chi, unit,
ilabel, intensity)
if self.__unitindex == 3:
ap = self.__approxpoint(tth, chi)
message += " $%s$" % str(ap)
else:
message = "x, y = [%s, %s], %s = %.2f" % (
x, y, ilabel, intensity)
elif self.__unitindex in [0, 1]:
qa = None
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
qa = self.__settings.ai.qFunction(
np.array([y - 0.5]), np.array([x - 0.5])),
if len(qa):
qa = qa[0]
if qa is not None and chi is not None:
unit = u"1/\u212B"
chi = chi * 180./math.pi
if self.__unitindex == 0:
unit = "1/nm"
if self.__unitindex == 1:
qa = qa / 10.
message = "x, y = [%s, %s], q = %f %s, chi = %f %s," \
" %s = %.2f" \
% (x, y, qa, unit,
chi, "deg",
ilabel, intensity)
else:
message = "x, y = [%s, %s], %s = %.2f" % (
x, y, ilabel, intensity)
elif self.__unitindex in [4]:
ra = None
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai is not None:
ra = self.__settings.ai.rFunction(
np.array([y - 0.5]), np.array([x - 0.5])),
if len(ra):
ra = ra[0] * 1000.
if ra is not None and chi is not None:
chi = chi * 180./math.pi
message = "x, y = [%s, %s], r = %f %s, chi = %f %s," \
" %s = %.2f" \
% (x, y, ra, "mm",
chi, "deg",
ilabel, intensity)
else:
message = "x, y = [%s, %s], %s = %.2f" % (
x, y, ilabel, intensity)
elif self.__unitindex in [5]:
cx = self.__settings.centerx
cy = self.__settings.centery
ra = math.sqrt((cx - x)**2 + (cy - y)**2)
if ra is not None and chi is not None:
chi = chi * 180./math.pi
message = "x, y = [%s, %s], r = %f %s, chi = %f %s," \
" %s = %.2f" \
% (x, y, ra, "pixel",
chi, "deg",
ilabel, intensity)
else:
message = "x, y = [%s, %s], %s = %.2f" % (
x, y, ilabel, intensity)
elif self.__plotindex > 0:
ix, iy, intensity, x, y = self._mainwidget.currentIntensity()
ilabel = self._mainwidget.scalingLabel()
pindex = self.__ui.mainplotComboBox.currentIndex()
pos = 0.0
sc = 1.0
tst = []
ts = ""
if len(self.__xbuffers) >= pindex and \
self.__xbuffers[pindex - 1]:
pos, sc = self.__xbuffers[pindex - 1]
xc = pos + x * sc
if len(self.__timestamps) >= pindex and \
self.__timestamps[pindex - 1]:
tst = self.__timestamps[pindex - 1]
it = int(iy)
# itx = int(ix)
if it < len(tst) and it >= 0:
ts = tst[it]
# store first tst[0]
# ts = tst[it] - tst[0]
units = self.__units[self.__unitindex]
xlabel = self.__ui.unitComboBox.currentText()
if "[" in xlabel:
xlabel, units = xlabel.split("[", 1)
units = units.replace("]", "")
message = "no: %s, %s = %s %s, %s = %.2f (time = %s s)" % (
it, xlabel, xc, units, ilabel, intensity, ts)
self._mainwidget.setDisplayedText(message)
# @debugmethod
def __tipmessage(self):
""" provides geometry messate
:returns: geometry text
:rtype: :obj:`unicode`
"""
tips = ""
with QtCore.QMutexLocker(self.__settings.aimutex):
if self.__settings.ai:
tips = str(self.__settings.ai)
return tips
# @debugmethod
[docs] @QtCore.pyqtSlot()
def updateGeometryTip(self):
""" update geometry tips
"""
message = self.__tipmessage()
self.__ui.calibrationPushButton.setToolTip(
"Input physical parameters\n%s" % message)
# @debugmethod
@QtCore.pyqtSlot()
def _showhideDiff(self):
""" show or hide diffractogram
"""
if not self.__showdiff:
self.__showdiff = True
self.__ui.showPushButton.setText("Stop")
self._plotDiff()
else:
self.__showdiff = False
self.__ui.showPushButton.setText("Show")
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _plotDiff(self):
""" plot all diffractograms
:returns: (list of x's for each diffractogram,
list of y's for each diffractogram,
integer timestamp)
:rtype: (:obj:`list` < :obj:`list` <float>>,
:obj:`list` < :obj:`list` <float>>,
int)
"""
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
xl = []
yl = []
timestamp = time.time()
if aistat:
if self._mainwidget.currentTool() == self.name:
if self.__settings.sendresults:
xl = []
yl = []
pxl = []
pyl = []
pel = []
nrplots = self.__ui.diffSpinBox.value()
if self.__nrplots != nrplots:
while nrplots > len(self.__curves):
self.__curves.append(self._mainwidget.onedbottomplot())
for i in range(nrplots):
self.__curves[i].show()
for i in range(nrplots, len(self.__curves)):
self.__curves[i].hide()
self.__nrplots = nrplots
if nrplots:
for i, cr in enumerate(self.__curves):
if i < nrplots:
colors = self.__colors
clr = tuple(colors[i % len(colors)]) \
if colors else self.__defpen
cr.setPen(_pg.mkPen(clr))
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None:
trans = self._mainwidget.transformations()[0]
csa = self.__settings.correctsolidangle
unit = self.__units[self.__unitindex]
if self.__unitindex in [5]:
unit = "r_mm"
else:
unit = self.__units[self.__unitindex]
dts = dts if trans else dts.T
mask = None
if dts.dtype.kind == 'f' and np.isnan(dts.min()):
mask = np.isnan(dts)
dts = np.array(dts)
dts[mask] = 0.
if mask is not None:
mask = mask.astype("int")
if self.__settings.showhighvaluemask and \
self._mainwidget.maskValue() is not None and \
self._mainwidget.maskValueIndices() is not None:
if mask is None:
mask = np.zeros(dts.shape)
try:
mask[self._mainwidget.maskValueIndices().T] = 1
except Exception as e:
logger.warning(str(e))
return xl, yl, timestamp
if self.__settings.showmask and \
self._mainwidget.applyMask() and \
self._mainwidget.maskIndices() is not None:
if mask is None:
mask = np.zeros(dts.shape)
try:
mask[self._mainwidget.maskIndices().T] = 1
except Exception as e:
logger.warning(str(e))
return xl, yl, timestamp
for i in range(nrplots):
try:
with QtCore.QMutexLocker(self.__settings.aimutex):
res = self.__settings.ai.integrate1d(
dts,
self.__settings.diffnpt,
correctSolidAngle=csa,
radial_range=(
self.__radrange[i]
if len(self.__radrange) > i else None),
azimuth_range=(
self.__azrange[i]
if len(self.__azrange) > i else None),
unit=unit, mask=mask)
# print(res)
x = res[0]
y = res[1]
if self.__unitindex in [5]:
with QtCore.QMutexLocker(
self.__settings.aimutex):
aif = self.__settings.ai.getFit2D()
if aif["pixelX"] and aif["pixelY"]:
if self.__azrange[i] is None:
azs, aze = 0, math.pi/2
else:
azs, aze = self.__azrange[i]
azs *= math.pi / 180.
aze *= math.pi / 180.
facx = 1000./aif["pixelX"]
facy = 1000./aif["pixelY"]
with QtCore.QMutexLocker(
self.__settings.aimutex):
cs1 = math.cos(
azs + self.__settings.ai.rot3)
cs2 = math.cos(
aze + self.__settings.ai.rot3)
sn1 = math.sin(
azs + self.__settings.ai.rot3)
sn2 = math.sin(
aze + self.__settings.ai.rot3)
fc1 = facx * cs1 / math.cos(
self.__settings.ai.rot1)
fc2 = facx * cs2 / math.cos(
self.__settings.ai.rot1)
fs1 = facy * sn1 / math.cos(
self.__settings.ai.rot2)
fs2 = facy * sn2 / math.cos(
self.__settings.ai.rot2)
x = [
(math.sqrt(
(fc1 * r)**2 + (fs1 * r)**2)
+ math.sqrt(
(fc2 * r)**2 + (fs2 * r)**2))
/ 2
for r in x]
self.__curves[i].setData(x=x, y=y)
if self.__settings.sendresults or \
self.__accumulate:
xl.append([float(e) for e in x])
yl.append([float(e) for e in y])
if self.__settings.sendresults:
px, py, pe = self.__findpeaks2(x, y)
pxl.append([float(e) for e in px])
pyl.append([float(e) for e in py])
pel.append(float(pe))
except Exception as e:
# print(str(e))
logger.warning(str(e))
x = []
y = []
self.__curves[i].setData(x=x, y=y)
self.__curves[i].setVisible(True)
else:
for i in range(nrplots):
self.__curves[i].setVisible(False)
if self.__settings.sendresults:
self.__sendresults(xl, yl, pxl, pyl, pel, timestamp)
return xl, yl, timestamp
def __sendresults(self, xl, yl, pxl=None, pyl=None, pel=None,
timestamp=None):
""" send results to LavueController
:param xl: list of x's for each diffractogram
:type xl: :obj:`list` < :obj:`list` <float>>
:param yl: list of values for each diffractogram
:type yl: :obj:`list` < :obj:`list` <float>>
:param pxl: list of peak x's for each diffractogram
:type pxl: :obj:`list` < :obj:`list` <float>>
:param pyl: list of peak values for each diffractogram
:type pyl: :obj:`list` < :obj:`list` <float>>
:param pel: peak x's errors for each diffractogram
:type pel: :obj:`list` <float>
:param timestamp: timestamp
:type timestamp: :obj:`int`
"""
results = {"tool": self.alias}
npl = len(xl)
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = timestamp
results["nrdiffs"] = len(xl)
results["calibration"] = self.__settings.calibrationfilename
for i in range(npl):
results["radial_range_%s" % (i + 1)] = self.__radrange[i] \
if len(self.__radrange) > i else None
results["azimuth_range_%s" % (i + 1)] = self.__azrange[i] \
if len(self.__azrange) > i else None
results["diff_%s" % (i + 1)] = [xl[i], yl[i]]
if pxl is not None and pyl is not None:
if len(pxl) > i and len(pyl) > i:
results["peaks_%s" % (i + 1)] = [pxl[i], pyl[i]]
if pel is not None:
results["peaks_%s_error" % (i + 1)] = pel[i]
results["unit"] = self.__units[self.__unitindex]
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
def __findpeaks(self, x, y, nr=20):
""" find peaks from diffractogram
:param x: x of diffractogram
:type x: :obj:`list` <float>>
:param y: values of diffractogram
:type y: :obj:`list` <float>>
:param nr: list of peak x's for each diffractogram
:type nr: :obj:`int`
:returns: (peak_xs, peak_values, x_error)
:rtype: (:obj:`list` <float>, :obj:`list` <float>, :obj:`float`)
"""
t = np.array(y)
xml = []
yml = []
iml = []
eml = []
it = 0
while (t > 0).any() and it < nr:
im = np.argmax(t)
tm = t[im]
iml.append(im)
xml.append(x[im])
yml.append(tm)
er = max(x[im] - x[max(im - 1, 0)],
x[min(im + 1, len(x) - 1)] - x[im])
eml.append(er)
it += 1
# print("%s. found %s (%s +- %s, %s)" % (it, im, x[im], er, tm))
i1 = im
while i1 and t[i1 - 1] < t[i1]:
i1 -= 1
ib = i1
i1 = im
while i1 + 1 < len(t) and t[i1 + 1] < t[i1]:
i1 += 1
ie = i1
# print(t)
# print("cut: [%s: %s]" % (ib, ie))
t[ib:(ie + 1)] = 0
# print(t)
return ([float(e) for e in xml],
[float(e) for e in yml],
max(eml))
def __findpeaks2(self, x, y, nr=20):
""" find peaks from diffractogram with scipy
:param x: x of diffractogram
:type x: :obj:`list` <float>>
:param y: values of diffractogram
:type y: :obj:`list` <float>>
:param nr: list of peak x's for each diffractogram
:type nr: :obj:`int`
:returns: (peak_xs, peak_values, x_error)
:rtype: (:obj:`list` <float>, :obj:`list` <float>, :obj:`float`)
"""
f = scipy.interpolate.InterpolatedUnivariateSpline(x, y, k=4)
xml = f.derivative().roots()
yml = f(xml)
er = max([(x[i+1] - x[i]) for i in range(len(x) - 1)])
iml = np.argpartition(yml, -nr)[-nr:]
iml = iml[np.argsort(-yml[iml])]
iml = iml[:nr]
return ([float(e) for e in xml[iml]],
[float(e) for e in yml[iml]],
er)
# @debugmethod
@QtCore.pyqtSlot()
def _setPolarRange(self):
""" launches range widget
:returns: apply status
:rtype: :obj:`bool`
"""
nrplots = self.__ui.diffSpinBox.value()
if nrplots:
cnfdlg = diffRangeDialog.DiffRangeTabDialog(nrplots)
cnfdlg.azstart = self.__azstart
cnfdlg.azend = self.__azend
cnfdlg.radstart = self.__radstart
cnfdlg.radend = self.__radend
cnfdlg.radunitindex = 2
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__azstart = cnfdlg.azstart
self.__azend = cnfdlg.azend
self.__radstart = cnfdlg.radstart
self.__radend = cnfdlg.radend
self.__updateregion()
self._resetAccu()
self._mainwidget.emitTCC()
# @debugmethod
def _updatePolarRange(self, ranges):
""" update diffractogram ranges
:param ranges: list of [azimuth_start, azimuth_end,
radial_start, radial_end]
elements for each diffractograms
:type ranges: :obj:`list` < [
:obj:`float`, :obj:`float`,
:obj:`float`, :obj:`float` ]>
"""
nrplots = self.__ui.diffSpinBox.value()
if nrplots:
azstart = []
azend = []
radstart = []
radend = []
for rn in ranges:
azstart.append(rn[0])
azend.append(rn[1])
radstart.append(rn[2])
radend.append(rn[3])
self.__azstart = azstart
self.__azend = azend
self.__radstart = radstart
self.__radend = radend
self.__updateregion()
self._resetAccu()
self._mainwidget.emitTCC()
def __updateaz(self):
""" update azimuth range in deg
"""
nrplots = self.__ui.diffSpinBox.value()
self.__azrange = []
for _ in range(len(self.__azend), nrplots):
self.__azend.append(None)
for _ in range(len(self.__azstart), nrplots):
self.__azstart.append(None)
for i in range(nrplots):
if self.__azend[i] is None and self.__azstart[i] is None:
self.__azrange.append(None)
elif (self.__azend[i] is not None
or self.__azstart[i] is not None):
if self.__azstart[i] is None:
self.__azstart[i] = 0
if self.__azend[i] is None:
self.__azend[i] = 360
self.__azrange.append([self.__azstart[i], self.__azend[i]])
def __updaterad(self):
"""update radial range in deg
"""
nrplots = self.__ui.diffSpinBox.value()
self.__radrange = []
for _ in range(len(self.__radend), nrplots):
self.__radend.append(None)
for _ in range(len(self.__radstart), nrplots):
self.__radstart.append(None)
for i in range(nrplots):
if self.__radend[i] is not None or \
self.__radstart[i] is not None:
if self.__radstart[i] is None:
self.__radstart[i] = 0
if self.__radend[i] is None:
self.__radend[i] = 90
if self.__radend[i] is None or self.__radstart[i] is None:
self.__radrange.append(None)
else:
if self.__unitindex == 2:
self.__radrange.append(
[self.__radstart[i], self.__radend[i]])
else:
rs = self.__radstart[i] * math.pi / 180.
re = self.__radend[i] * math.pi / 180.
if self.__unitindex < 2:
oneoverlength = self.__settings.energy / 12398.4193
fac = 4 * math.pi * oneoverlength
qs = fac * math.sin(rs/2.)
qe = fac * math.sin(re/2.)
if self.__unitindex == 0:
qs = qs * 10.
qe = qe * 10.
self.__radrange.append([qs, qe])
elif self.__unitindex == 3:
self.__radrange.append([rs, re])
elif self.__unitindex in [4, 5]:
try:
[rbb, rbe, reb, ree, _, _, _, _] = \
self.__getcorners(
self.__radstart[i],
self.__radend[i],
self.__azstart[i],
self.__azend[i])
logger.debug(
"RESULT %s %s %s" % (
str(rbb.x), rbb.success, rbb.fun))
logger.debug(
"RESULT %s %s %s" % (
str(rbe.x), rbe.success, rbe.fun))
logger.debug(
"RESULT %s %s %s" % (
str(reb.x), reb.success, reb.fun))
logger.debug(
"RESULT %s %s %s" % (
str(ree.x), ree.success, ree.fun))
rsl = []
rel = []
if rbb.success:
with QtCore.QMutexLocker(
self.__settings.aimutex):
ra = self.__settings.ai.rFunction(
np.array([rbb.x[1] - 0.5]),
np.array([rbb.x[0] - 0.5]))
if len(ra):
rsl.append(ra[0] * 1000.)
if rbe.success:
with QtCore.QMutexLocker(
self.__settings.aimutex):
ra = self.__settings.ai.rFunction(
np.array([rbe.x[1] - 0.5]),
np.array([rbe.x[0] - 0.5]))
if len(ra):
rsl.append(ra[0] * 1000.)
if reb.success:
with QtCore.QMutexLocker(
self.__settings.aimutex):
ra = self.__settings.ai.rFunction(
np.array([reb.x[1] - 0.5]),
np.array([reb.x[0] - 0.5]))
if len(ra):
rel.append(ra[0] * 1000.)
if ree.success:
with QtCore.QMutexLocker(
self.__settings.aimutex):
ra = self.__settings.ai.rFunction(
np.array([ree.x[1] - 0.5]),
np.array([ree.x[0] - 0.5]))
if len(ra):
rel.append(ra[0] * 1000.)
if rsl:
rs = np.mean(rsl)
else:
rs = math.tan(rs) * self.__settings.detdistance
if rel:
re = np.mean(rel)
else:
re = math.tan(re) * self.__settings.detdistance
except Exception as e:
logger.debug(str(e))
rs = math.tan(rs) * self.__settings.detdistance
re = math.tan(re) * self.__settings.detdistance
self.__radrange.append([rs, re])
def __updateregion(self):
""" update diffractogram region
"""
self.__updateaz()
self.__updaterad()
self.updateRangeTip()
self.runProgress(["findregions"], "_updateRegionsAndPlot")
# @debugmethod
[docs] def findregions(self):
""" find regions lists
"""
nrplots = self.__ui.diffSpinBox.value()
regions = []
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
for i in range(nrplots):
if ((self.__azrange and self.__azrange[i]) or
(self.__radrange and self.__radrange[i])) and aistat:
azstart = self.__azstart[i] \
if self.__azstart[i] is not None else 0
azend = self.__azend[i] \
if self.__azend[i] is not None else 360
radstart = self.__radstart[i] \
if self.__radstart[i] is not None else 0
radend = self.__radend[i] \
if self.__radend[i] is not None else 70
try:
regions.append(
self.__findregion(radstart, radend, azstart, azend))
except Exception as e:
try:
logger.warning(str(e))
# print(str(e))
regions.append(
self.__findregion2(
radstart, radend, azstart, azend))
except Exception as e2:
logger.warning(str(e2))
# print(str(e2))
# regions.append([])
self.__regions = regions
# @debugmethod
def _updateRegionsAndPlot(self, regions=None):
""" update regions and plots
:param regions: list of region lines
:type regions: :obj:`list` < :obj:`list` < :obj:`list`
< (:obj:`float`, :obj:`float`) > > >
"""
regions = regions if regions is not None else self.__regions
self._mainwidget.updateRegions(regions)
self._resetAccu()
self._plotDiff()
self._closeReset()
# @debugmethod
def _closeReset(self):
""" close reset method for progressbar
:returns: progress status
:rtype: :obj:`bool`
"""
status = True
logger.debug("closing Progress")
if self.__progress:
self.__progress.reset()
if self.__commandthread and self.__commandthread.error:
logger.error(
"Problems in updating Channels %s" %
str(self.__commandthread.error))
self.__commandthread.error = None
status = False
if self.__progress:
self.__progress.setParent(None)
self.__progress = None
self.waitForThread()
logger.debug("closing Progress ENDED")
return status
def __findregion2(self, radstart, radend, azstart, azend):
""" find region defined by angle range using image masking method
:param radstart: start of radial region
:type radstart: :obj:`float`
:param radend: end of radial region
:type radend: :obj:`float`
:param azstart: start of azimuth region
:type azstart: :obj:`float`
:param azend: end of azimuth region
:type azend: :obj:`float`
:returns: list of region lines
:rtype: :obj:`list` < :obj:`list` < (float, float) > >
"""
if azend - azstart >= 360:
azstart = 0
azend = 360
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
if aistat:
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None and dts.shape and len(dts.shape) == 2:
shape = dts.T.shape
else:
shape = [1000., 1000.]
with QtCore.QMutexLocker(self.__settings.aimutex):
tta = self.__settings.ai.twoThetaArray(shape)
cha = self.__settings.ai.chiArray(shape)
rb = self.__degtrim(radstart, 0, 360) * math.pi / 180.
re = self.__degtrim(radend, 0, 360) * math.pi / 180.
ab = self.__degtrim(azstart, -180, 180) * math.pi / 180.
ae = self.__degtrim(azend, -180, 180) * math.pi / 180.
lines = []
if azend - azstart < 360:
chmask = (cha < ab) | (cha > ae)
ttaa = (tta - rb) * (tta - re)
ttaa[chmask] = 6
rblines = functions.isocurve(
ttaa, 0, connected=True)
logger.debug("RUN1 %s " % len(rblines))
thmask = (tta < rb) | (tta > re)
chaa = (cha - ab) * (cha - ae)
chaa[thmask] = 6
ablines = functions.isocurve(
chaa, 0, connected=True)
logger.debug("RUN2 %s " % len(ablines))
else:
ttaa = (tta - rb) * (tta - re)
rblines = functions.isocurve(
ttaa, 0, connected=True)
logger.debug("RUN4 %s " % len(rblines))
for line in rblines:
lines.append([(p[1], p[0]) for p in line])
if azend - azstart < 360:
for line in ablines:
lines.append([(p[1], p[0]) for p in line])
# for line in relines:
# lines.append([(p[1], p[0]) for p in line])
# print(lines)
return lines
# self._mainwidget.updateRegions(lines)
else:
return []
# self._mainwidget.updateRegions([[(0, 0)]])
def __findregion(self, radstart, radend, azstart, azend):
""" find region defined by angle range using Newton method
:param radstart: start of radial region
:type radstart: :obj:`float`
:param radend: end of radial region
:type radend: :obj:`float`
:param azstart: start of azimuth region
:type azstart: :obj:`float`
:param azend: end of azimuth region
:type azend: :obj:`float`
:returns: list of region lines
:rtype: :obj:`list` < :obj:`list` < (float, float) > >
"""
if azend - azstart >= 360:
azstart = 0
azend = 360
[rbb, rbe, reb, ree, rb, re, ab, ae] = self.__getcorners(
radstart, radend, azstart, azend)
azb = azstart * math.pi / 180.
aze = azend * math.pi / 180.
logger.debug("RESULT %s %s %s" % (str(rbb.x), rbb.success, rbb.fun))
logger.debug("RESULT %s %s %s" % (str(rbe.x), rbe.success, rbe.fun))
logger.debug("RESULT %s %s %s" % (str(reb.x), reb.success, reb.fun))
logger.debug("RESULT %s %s %s" % (str(ree.x), ree.success, ree.fun))
# print("RESULT %s %s %s" % (str(rbb.x), rbb.success, rbb.fun))
# print("RESULT %s %s %s" % (str(rbe.x), rbe.success, rbe.fun))
# print("RESULT %s %s %s" % (str(reb.x), reb.success, reb.fun))
# print("RESULT %s %s %s" % (str(ree.x), ree.success, ree.fun))
lines = []
if azend - azstart < 360:
pbbeb = self.__findfixchipath(rbb.x, reb.x, ab, rb, re)
lines.append(pbbeb)
pbeee = self.__findfixchipath(rbe.x, ree.x, ae, rb, re)
lines.append(pbeee)
if self.__radstart[0] > 0:
pbbbe = self.__findfixradpath(
rbb.x, rbe.x, rb, azb, aze,
full=(azend - azstart >= 360))
lines.append(pbbbe)
if self.__radend[0] < 60:
pebee = self.__findfixradpath(
reb.x, ree.x, re, azb, aze,
full=(azend - azstart >= 360))
lines.append(pebee)
return lines
@classmethod
def __degtrim(cls, vl, lowbound, upbound):
""" trim angle value to bounds in deg
:param vl: value to trim
:type vl: :obj:`float`
:param lowbound: lower bound of value
:type lowbound: :obj:`float`
:param upbound: upper blund of value
:type upbound: :obj:`float`
:returns: trimmed value
:rtype: :obj:`float`
"""
while vl >= upbound:
vl -= 360
while vl < lowbound:
vl += 360
return vl
@classmethod
def __radtrim(cls, vl, lowbound, upbound):
""" trim angle value to bounds in rad
:param vl: value to trim
:type vl: :obj:`float`
:param lowbound: lower bound of value
:type lowbound: :obj:`float`
:param upbound: upper blund of value
:type upbound: :obj:`float`
:returns: trimmed value
:rtype: :obj:`float`
"""
while vl >= upbound:
vl -= 2 * math.pi
while vl < lowbound:
vl += 2 * math.pi
return vl
def __findfixchipath(self, xstart, xend, chi, radstart, radend,
step=4, fmax=1.e-10):
""" find a path for fix azimuth angle
:param xstart: start point
:type xstart: :obj:`float`
:param xend: end point
:type xend: :obj:`float`
:param chi: chi angle value
:type chi: :obj:`float`
:param radstart: radial angle start value
:type radstart: :obj:`float`
:param radend: radial angle end value
:type radend: :obj:`float`
:param fmax: maximal allowed value for the test function
:type fmax: :obj:`float`
:returns: list of points
:rtype: :obj:`list` < (float, float) >
"""
points = [tuple(xstart)]
if self.__dist2(xstart, xend) < step * step:
points.append(tuple(xend))
return points
alphas = []
cut = None
tchi = self.__radtrim(chi, -math.pi, math.pi)
if tchi < -math.pi/2 or tchi > math.pi/2:
cut = 0
cchi = self.__chi(xstart, cut)
x = xstart
def rsfun(alpha, x, step, cut, chi):
y = [x[0] + step * math.cos(alpha),
x[1] + step * math.sin(alpha)]
return [self.__chi(y, cut) - chi]
res = scipy.optimize.root(rsfun, cchi, (x, step, cut, cchi))
y = [x[0] + step * math.cos(res.x[0]),
x[1] + step * math.sin(res.x[0])]
if self.__tth(x) - self.__tth(y) > 0 or \
(not res.success and abs(res.fun) > fmax):
res = scipy.optimize.root(rsfun, - cchi, (x, step, cut, cchi))
y = [x[0] + step * math.cos(res.x[0]),
x[1] + step * math.sin(res.x[0])]
if self.__tth(x) - self.__tth(y) > 0 or \
(not res.success and abs(res.fun) > fmax):
raise Exception("__findfixchipath: Cannot find the next point")
points.append(tuple(y))
if self.__dist2(y, xend) < step * step:
points.append(tuple(xend))
return points
alphas.append(res.x[0])
waking = True
maxit = 10000
it = 0
istep = step
while waking and maxit > it:
it += 1
alp = self.__fitnext(alphas[-5:])
x = y
res = scipy.optimize.root(rsfun, alp, (x, istep, cut, cchi))
y = [x[0] + istep * math.cos(res.x[0]),
x[1] + istep * math.sin(res.x[0])]
if self.__tth(x) - self.__tth(y) > 0 or \
(not res.success and abs(res.fun) > fmax):
istep = step / 2.
continue
points.append(tuple(y))
if self.__dist2(y, xend) < istep * istep:
waking = False
alphas.append(res.x[0])
istep = step
points.append(tuple(xend))
return points
def __findfixradpath(self, xstart, xend, rad, azstart, azend,
step=4, fmax=1.e-10, full=False):
""" find a path for fixed radial angle
:param xstart: start point
:type xstart: :obj:`float`
:param xend: end point
:type xend: :obj:`float`
:param rad: radial angle value
:type rad: :obj:`float`
:param azstart: azimuth angle start value
:type azstart: :obj:`float`
:param azend: azimuth angle end value
:type azend: :obj:`float`
:param fmax: maximal allowed value for the test function
:type fmax: :obj:`float`
:param full: full angle flag
:type full: :obj:`bool`
:returns: list of points
:rtype: :obj:`list` < (float, float) >
"""
aze = azend
points = [tuple(xstart)]
while azstart > aze:
aze += 2 * math.pi
alphas = []
cut = None
tth1 = self.__tth(xstart)
tth2 = self.__tth([xstart[0] + 1, xstart[1]])
tth3 = self.__tth([xstart[0], xstart[1] + 1])
dth = max(abs(tth1 - tth2), abs(tth1 - tth3))
if (rad/dth) < 10. * step:
step = (rad / dth) / 10.
elif (rad/dth) > 10000. * step:
step = (rad / dth) / 10000.
if ((self.__dist2(xstart, xend) < step * step
and abs(azstart - aze) < math.pi) or step < 1.e-6):
points.append(tuple(xend))
return points
# tth = self.__tth(xstart)
tchi = self.__chi(xstart)
if tchi < -math.pi/2 or tchi > math.pi/2:
cut = 0
cchi = self.__chi(xstart, cut)
x = xstart
logger.debug("CUT %s %s " % (cut, cchi))
def rsfun(alpha, x, step, cut, tth):
y = [x[0] + step * math.cos(alpha),
x[1] + step * math.sin(alpha)]
return [self.__tth(y) - tth]
itm = 10
it = 0
istep = step
while it < itm:
it += 1
res = scipy.optimize.root(rsfun, cchi + math.pi/2,
(x, istep, cut, rad))
y = [x[0] + istep * math.cos(res.x[0]),
x[1] + istep * math.sin(res.x[0])]
if self.__chi(x, cut) - self.__chi(y, cut) > 0 or \
(not res.success and abs(res.fun) > fmax):
res = scipy.optimize.root(rsfun, -cchi - math.pi/2,
(x, istep, cut, rad))
y = [x[0] + istep * math.cos(res.x[0]),
x[1] + istep * math.sin(res.x[0])]
if self.__chi(x, cut) - self.__chi(y, cut) > 0 or \
(not res.success and abs(res.fun) > fmax):
istep = istep / 2.
else:
break
else:
break
if it == itm:
raise Exception("__findfixradpath: Cannot find the next point")
points.append(tuple(y))
if self.__dist2(y, xend) < step * step and not full:
points.append(tuple(xend))
return points
alphas.append(res.x[0])
waking = True
maxit = 10000
it = 0
istep = step
while waking and maxit > it:
it += 1
alp = self.__fitnext(alphas[-5:])
x = y
tchi = self.__chi(x, cut)
if tchi < -math.pi/2 or tchi > math.pi/2:
cut = 0
else:
cut = None
res = scipy.optimize.root(rsfun, alp, (x, istep, cut, rad))
y = [x[0] + istep * math.cos(res.x[0]),
x[1] + istep * math.sin(res.x[0])]
if self.__chi(x, cut) - self.__chi(y, cut) > 0 or \
(not res.success and abs(res.fun) > fmax):
istep = step / 2.
continue
points.append(tuple(y))
if self.__dist2(y, xend) < istep * istep:
waking = False
alphas.append(res.x[0])
istep = step
points.append(tuple(xend))
return points
def __fitnext(self, y, x=None, x0=None):
""" fits next y value
:param y: list of y values
:type y: :obj:`list` <:obj:`float`>
:param x: list of x values
:type x: :obj:`list` <:obj:`float`>
:param x0: next x value
:type x0: :obj:`float`
:returns: next y value
:rtype: :obj:`float`
"""
n = len(y)
if x is None:
x = range(n)
x0 = n
return np.poly1d(np.polyfit(x, y, n - 1))(x0)
def __dist2(self, x, y):
""" distance square of x and y
:param x: 2d point x
:type x: :obj:`list` <:obj:`float`>
:param y: 2d point y
:type y: :obj:`list` <:obj:`float`>
:returns: distance square
:rtype: :obj:`float`
"""
d0 = x[0] - y[0]
d1 = x[1] - y[1]
return d0 * d0 + d1 * d1
def __chi(self, x, cut=None):
""" chi of left bottom pixel corner in rad
:param x: 2d point x
:type x: :obj:`list` <:obj:`float`>
:param cut: cut position in rad
:type cut: :obj:`float`
:returns: chi value
:rtype: :obj:`float`
"""
with QtCore.QMutexLocker(self.__settings.aimutex):
chi = float(self.__settings.ai.chi(
np.array([x[1] - 0.5]), np.array([x[0] - 0.5]))[0])
if cut is not None and chi > cut and cut > -math.pi:
chi += 2 * math.pi
return chi
def __tth(self, x, path=None):
""" tth of left bottom pixel corner in rad
:param x: 2d point x
:type x: :obj:`list` <:obj:`float`>
:param path: path method
:type path: :obj:`str`
:returns: chi value
:rtype: :obj:`float`
"""
with QtCore.QMutexLocker(self.__settings.aimutex):
tth = self.__settings.ai.tth(
np.array([x[1] - 0.5]), np.array([x[0] - 0.5]), path=path)[0]
return float(tth)
def __findpoint(self, rd, az, shape, start=None, itmax=20, fmax=1e-9):
""" find a point in pixels
:param rd: radial coordinate
:type rd: :obj:`float`
:param az: azimuth coordinate
:type az: :obj:`float`
:param shape: shape of the image
:type shape: [:obj:`float`, :obj:`float`]
:param start: start coordinate
:type start: [:obj:`float`, :obj:`float`]
:param itmaxstart: maximal number of tries
:type start: :obj:`int`
:param fmax: maximal allowed value for the test function
:type fmax: :obj:`float`
"""
def rafun(x, f1, f2):
return [self.__tth(x) - f1, self.__chi(x) - f2]
found = False
it = 0
if start is None:
start = self.__approxpoint(rd, az)
while not found and it < itmax:
res = scipy.optimize.root(rafun, start, (rd, az))
f = res.fun
f2 = f[0] * f[0] + f[1] * f[1]
found = res.success and f2 < fmax
it += 1
start = [random.randint(0, shape[0]),
random.randint(0, shape[1])]
logger.debug("Tries: %s" % it)
logger.debug(res)
return res
def __getcorners(self, radstart, radend, azstart, azend):
""" find region corners
:param radstart: start of radial region in deg
:type radstart: :obj:`float`
:param radend: end of radial region in deg
:type radend: :obj:`float`
:param azstart: start of azimuth region in deg
:type azstart: :obj:`float`
:param azend: end of azimuth region in deg
:type azend: :obj:`float`
:returns: [rbb, rbe, reb, ree, rb, re, ab, ae]
where rbb, rbe, reb, ree
are result objects of found region corners
while rb, re, ab, ae
are input angles in radians
:rtype: :obj:`list` <:obj:`float`>
"""
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
if aistat:
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None and dts.shape and len(dts.shape) == 2:
shape = dts.shape
else:
shape = [1000., 1000.]
rb = self.__degtrim(radstart, 0, 360) * math.pi / 180.
re = self.__degtrim(radend, 0, 360) * math.pi / 180.
ab = self.__degtrim(azstart, -180, 180) * math.pi / 180.
ae = self.__degtrim(azend, -180, 180) * math.pi / 180.
rbb = self.__findpoint(rb, ab, shape)
rbe = self.__findpoint(rb, ae, shape)
ree = self.__findpoint(re, ae, shape)
reb = self.__findpoint(re, ab, shape)
return [rbb, rbe, reb, ree, rb, re, ab, ae]
def __approxpoint(self, rad, az):
""" find approximate x,y coorinates from angles
:param rad: radial angle in rad
:type rad: :obj:`float`
:param az: azimuth angle in rad
:type az: :obj:`float`
:returns: [x, y] coordinates
:rtype: [:obj:`float`, :obj:`float`]
"""
with QtCore.QMutexLocker(self.__settings.aimutex):
aistat = self.__settings.ai is not None
if aistat:
tnr = math.tan(rad)
csa = math.cos(az)
sna = math.sin(az)
dst = self.__settings.detdistance
xc = self.__settings.centerx
yc = self.__settings.centery
px = self.__settings.pixelsizex / 1000.
py = self.__settings.pixelsizey / 1000.
y = (dst * sna * tnr + yc * py)/py
x = (dst * csa * tnr + xc * px)/px
return [x, y]
# @debugmethod
@QtCore.pyqtSlot(int)
def _setUnitIndex(self, uindex):
""" set unit index
:param uindex: unit index, i.e. q_nm^-1, q_A^-1, 2th_deg, 2th_rad
:type uindex: :obj:`int`
"""
if self.__unitindex != uindex:
self._resetAccu()
self.__unitindex = uindex
self.__updaterad()
self._plotDiff()
# @debugmethod
@QtCore.pyqtSlot(int)
def _setPlotIndex(self, pindex):
""" set plot index
:param pindex: plot index -> 0: Image, <i>: Buffer <i>
:type pindex: :obj:`int`
"""
if pindex and pindex > 0:
self.parameters.centerlines = False
self.parameters.toolscale = True
self.parameters.regions = False
if len(self.__xbuffers) >= pindex and \
self.__xbuffers[pindex - 1]:
pos, sc = self.__xbuffers[pindex - 1]
self._mainwidget.setToolScale([pos, 0], [sc, 1])
else:
self._mainwidget.setToolScale([0, 0], [1, 1])
# self._mainwidget.setToolScale([0, 0], [1, 1])
if not self.__plotindex:
self.__oldlocked = self._mainwidget.setAspectLocked(False)
else:
if self.__oldlocked is not None:
self._mainwidget.setAspectLocked(self.__oldlocked)
self.parameters.centerlines = True
self.parameters.toolscale = False
self.parameters.regions = True
if pindex is not None:
self.__plotindex = pindex
self._mainwidget.updateinfowidgets(self.parameters)
self._mainwidget.emitReplotImage()
# @debugmethod
[docs] @QtCore.pyqtSlot()
def updateRangeTip(self):
""" update geometry tips
"""
text = u""
nrplots = self.__ui.diffSpinBox.value()
for i in range(nrplots):
if nrplots > 1:
text += "%s. " % (i + 1)
text += \
u"Radial range: [%s, %s] deg; Azimuth range: [%s, %s] deg" % (
self.__radstart[i]
if (len(self.__radstart) > i and
self.__radstart[i] is not None)
else "0",
self.__radend[i]
if (len(self.__radend) > i and
self.__radend[i] is not None)
else "90",
self.__azstart[i]
if (len(self.__azstart) > i and
self.__azstart[i] is not None)
else "0",
self.__azend[i]
if (len(self.__azend) > i and
self.__azend[i] is not None)
else "360")
if i < nrplots - 1:
text += "\n"
self.__ui.rangePushButton.setToolTip(text)
[docs]class MaximaToolWidget(ToolBaseWidget):
""" maxima tool widget
"""
#: (:obj:`str`) tool name
name = "Maxima"
#: (:obj:`str`) tool name alias
alias = "maxima"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`int`) geometry space index -> 0: angle, 1 q-space
self.__gspaceindex = 0
#: (:obj:`int`) plot index -> 0: Cartesian, 1 polar-th, 2 polar-q
self.__plotindex = 0
#: (:class:`Ui_ROIToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _maximaformclass()
self.__ui.setupUi(self)
# self.parameters.lines = True
#: (:obj:`str`) infolineedit text
self.parameters.infolineedit = ""
self.parameters.infotips = ""
self.parameters.centerlines = True
self.parameters.toolscale = False
self.parameters.maxima = True
# self.parameters.rightplot = True
#: (`lavuelib..imageDisplayWidget.AxesParameters`) axes backup
self.__axes = None
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
#: (:obj:`float`) radail coordinate factor
self.__radmax = 1.
#: (:obj:`float`) polar coordinate factor
self.__polmax = 1.
#: (:obj:`bool`) reploting flag
self.__reploting = False
#: (:obj:`bool`) reploting flag
self.__updating = False
#: (:obj:`list`) last combo items
self.__lastcomboitems = []
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.angleqPushButton.clicked, self._setGeometry],
[self.__ui.angleqComboBox.currentIndexChanged,
self._setGSpaceIndex],
[self.__ui.angleqComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self._mainwidget.mouseImageDoubleClicked,
self._updateCenter],
[self.__ui.maximaComboBox.currentIndexChanged, self._replot],
[self.__ui.maximaComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self.__ui.numberSpinBox.valueChanged, self._replot],
[self.__ui.numberSpinBox.valueChanged, self._mainwidget.emitTCC],
[self._mainwidget.geometryChanged, self.updateGeometryTip],
[self._mainwidget.geometryChanged, self._mainwidget.emitTCC],
[self._mainwidget.mouseImagePositionChanged, self._message]
]
# @debugmethod
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "geometry" in cnf.keys():
try:
self._updateGeometry(cnf["geometry"])
except Exception as e:
# print(str(e))
logger.warning(str(e))
if "maxima_number" in cnf.keys():
try:
self.__ui.numberSpinBox.setValue(int(cnf["maxima_number"]))
except Exception as e:
logger.warning(str(e))
# print(str(e))
if "units" in cnf.keys():
idxs = ["angles", "q-space", "xy-space"]
xcrd = str(cnf["units"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.angleqComboBox.setCurrentIndex(idx)
if "current_maximum" in cnf.keys():
try:
cmx = int(cnf["current_maximum"]) - 1
self.__ui.maximaComboBox.setCurrentIndex(cmx)
except Exception as e:
# print(str(e))
logger.warning(str(e))
cmx = 0
# @debugmethod
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["units"] = str(
self.__ui.angleqComboBox.currentText()).lower()
cnf["maxima_number"] = self.__ui.numberSpinBox.value()
cnf["current_maximum"] = self.__ui.maximaComboBox.currentIndex() + 1
cnf["geometry"] = {
"centerx": self.__settings.centerx,
"centery": self.__settings.centery,
"energy": self.__settings.energy,
"pixelsizex": self.__settings.pixelsizex,
"pixelsizey": self.__settings.pixelsizey,
"detdistance": self.__settings.detdistance,
}
return json.dumps(cnf, cls=numpyEncoder)
[docs] def activate(self):
""" activates tool widget
"""
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
[docs] def beforeplot(self, array, rawarray):
""" command before plot
:param array: 2d image array
:type array: :class:`numpy.ndarray`
:param rawarray: 2d raw image array
:type rawarray: :class:`numpy.ndarray`
:return: 2d image array and raw image
:rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`)
"""
if rawarray is not None and rawarray.any():
while rawarray.ndim > 2:
rawarray = np.nanmean(rawarray, axis=2)
nr = self.__ui.numberSpinBox.value()
nr = min(nr, rawarray.size)
if nr > 0:
offset = [0.5, 0.5]
if self.__settings.nanmask:
if rawarray.dtype.kind == 'f' and \
np.isnan(rawarray.min()):
rawarray = np.nan_to_num(rawarray)
fidxs = np.argsort(rawarray, axis=None)[-nr:]
aidxs = [np.unravel_index(idx, rawarray.shape)
for idx in fidxs]
naidxs = aidxs
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
x, y, s1, s2 = self._mainwidget.scale(
useraxes=False, noNone=True)
naidxs = [(int(i * s1 + x), int(j * s2 + y))
for i, j in aidxs]
offset = [offset[0] * s1, offset[1] * s2]
maxidxs = [[naidxs[n][0], naidxs[n][1], rawarray[i, j]]
for n, (i, j) in enumerate(aidxs)]
current = self.__updatemaxima(maxidxs)
if current >= 0:
aidxs.append(aidxs.pop(len(naidxs) - current - 1))
self._mainwidget.setMaximaPos(naidxs, offset)
else:
self.__updatemaxima([])
self._mainwidget.setMaximaPos([])
self.__reploting = False
# @debugmethod
def __updatemaxima(self, maxidxs):
""" updates maxima in the combobox
:param maxidxs: list with [[xn,yn, maxn], ... [x1,y1, max1]]
:type maxidxs:
"""
self.__updating = True
combo = self.__ui.maximaComboBox
idx = combo.currentIndex()
if len(maxidxs) < idx:
idx = len(maxidxs)
if len(maxidxs) and idx < 0:
idx = 0
QtCore.QCoreApplication.processEvents()
trans = self._mainwidget.transformations()[0]
if trans:
comboitems = ["%s: %s at (%s, %s)" % (i + 1, vl[2], vl[1], vl[0])
for i, vl in enumerate(reversed(maxidxs))]
else:
comboitems = ["%s: %s at (%s, %s)" % (i + 1, vl[2], vl[0], vl[1])
for i, vl in enumerate(reversed(maxidxs))]
if self.__lastcomboitems != comboitems:
self.__lastcomboitems != comboitems
combo.clear()
# self.__reploting = True
combo.addItems(comboitems)
combo.setCurrentIndex(idx)
self.__updating = False
if self.__settings.sendresults:
self.__sendresults(maxidxs)
return idx
def __sendresults(self, maxidxs):
""" send results to LavueController
:param maxidxs: list with [[xn,yn, maxn], ... [x1,y1, max1]]
:type maxidxs:
"""
results = {"tool": self.alias}
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["maxima"] = maxidxs
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
@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`
"""
txdata = None
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
xdata, ydata, useraxes=False)
if txdata is not None:
xdata = txdata
ydata = tydata
self.__settings.centerx = float(xdata)
self.__settings.centery = float(ydata)
self._mainwidget.writeAttribute("BeamCenterX", float(xdata))
self._mainwidget.writeAttribute("BeamCenterY", float(ydata))
self._message()
self.updateGeometryTip()
@QtCore.pyqtSlot()
def _replot(self):
if not self.__reploting and not self.__updating:
self.__reploting = True
self._mainwidget.emitReplotImage(False)
@QtCore.pyqtSlot()
def _message(self):
""" provides geometry message
"""
message = ""
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
txdata = None
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
x, y, useraxes=False)
if txdata is not None:
x, y = txdata, tydata
ilabel = self._mainwidget.scalingLabel()
if self.__gspaceindex == 0:
thetax, thetay, thetatotal = self.__pixel2theta(x, y)
if thetax is not None:
message = "th_x = %f deg, th_y = %f deg," \
" th_tot = %f deg, %s = %.2f" \
% (thetax * 180 / math.pi,
thetay * 180 / math.pi,
thetatotal * 180 / math.pi,
ilabel, intensity)
elif self.__gspaceindex == 1:
qx, qy, q = self.__pixel2q(x, y)
if qx is not None:
message = u"q_x = %f 1/\u212B, q_y = %f 1/\u212B, " \
u"q = %f 1/\u212B, %s = %.2f" \
% (qx, qy, q, ilabel, intensity)
else:
message = "x = %.2f, y = %.2f, %s = %.2f" % (
x, y, ilabel, intensity)
self._mainwidget.setDisplayedText(message)
def __pixel2theta(self, xdata, ydata, xy=True):
""" converts coordinates from pixel positions to theta angles
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:param xy: flag
:type xy: :obj:`bool`
:returns: x-theta, y-theta, total-theta
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
thetax = None
thetay = None
thetatotal = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
xcentered = xdata - self.__settings.centerx
ycentered = ydata - self.__settings.centery
if xy:
thetax = math.atan(
xcentered * self.__settings.pixelsizex / 1000.
/ self.__settings.detdistance)
thetay = math.atan(
ycentered * self.__settings.pixelsizey / 1000.
/ self.__settings.detdistance)
r = math.sqrt(
(xcentered * self.__settings.pixelsizex / 1000.) ** 2
+ (ycentered * self.__settings.pixelsizey / 1000.) ** 2)
thetatotal = math.atan(
r / self.__settings.detdistance)
return thetax, thetay, thetatotal
def __pixel2q(self, xdata, ydata, xy=True):
""" converts coordinates from pixel positions to q-space coordinates
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:param xy: flag
:type xy: :obj:`bool`
:returns: q_x, q_y, q_total
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
qx = None
qy = None
q = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
thetax, thetay, thetatotal = self.__pixel2theta(
xdata, ydata, xy)
wavelength = 12398.4193 / self.__settings.energy
if xy:
qx = 4 * math.pi / wavelength * math.sin(thetax/2.)
qy = 4 * math.pi / wavelength * math.sin(thetay/2.)
q = 4 * math.pi / wavelength * math.sin(thetatotal/2.)
return qx, qy, q
def __tipmessage(self):
""" provides geometry messate
:returns: geometry text
:rtype: :obj:`unicode`
"""
return u"geometry:\n" \
u" center = (%s, %s) pixels\n" \
u" pixel_size = (%s, %s) \u00B5m\n" \
u" detector_distance = %s mm\n" \
u" energy = %s eV" % (
self.__settings.centerx,
self.__settings.centery,
self.__settings.pixelsizex,
self.__settings.pixelsizey,
self.__settings.detdistance,
self.__settings.energy
)
# @debugmethod
@QtCore.pyqtSlot()
def _setGeometry(self):
""" launches geometry widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = geometryDialog.GeometryDialog()
cnfdlg.centerx = self.__settings.centerx
cnfdlg.centery = self.__settings.centery
cnfdlg.energy = self.__settings.energy
cnfdlg.pixelsizex = self.__settings.pixelsizex
cnfdlg.pixelsizey = self.__settings.pixelsizey
cnfdlg.detdistance = self.__settings.detdistance
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__settings.centerx = cnfdlg.centerx
self.__settings.centery = cnfdlg.centery
self.__settings.energy = cnfdlg.energy
self.__settings.pixelsizex = cnfdlg.pixelsizex
self.__settings.pixelsizey = cnfdlg.pixelsizey
self.__settings.detdistance = cnfdlg.detdistance
self._mainwidget.writeAttribute(
"BeamCenterX", float(self.__settings.centerx))
self._mainwidget.writeAttribute(
"BeamCenterY", float(self.__settings.centery))
self._mainwidget.writeAttribute(
"Energy", float(self.__settings.energy))
self._mainwidget.writeAttribute(
"DetectorDistance",
float(self.__settings.detdistance))
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
if self.__plotindex:
self._mainwidget.emitReplotImage()
self._mainwidget.emitTCC()
# @debugmethod
@QtCore.pyqtSlot()
def _updateGeometry(self, geometry):
""" update geometry widget
:param geometry: geometry dictionary
:type geometry: :obj:`dict` < :obj:`str`, :obj:`list`>
"""
try:
if "centerx" in geometry.keys():
self.__settings.centerx = float(geometry["centerx"])
self._mainwidget.writeAttribute(
"BeamCenterX", float(self.__settings.centerx))
except Exception:
pass
try:
if "centery" in geometry.keys():
self.__settings.centery = float(geometry["centery"])
self._mainwidget.writeAttribute(
"BeamCenterY", float(self.__settings.centery))
except Exception:
pass
try:
if "energy" in geometry.keys():
self.__settings.energy = float(geometry["energy"])
self._mainwidget.writeAttribute(
"Energy", float(self.__settings.energy))
except Exception:
pass
try:
if "pixelsizex" in geometry.keys():
self.__settings.pixelsizex = float(geometry["pixelsizex"])
except Exception:
pass
try:
if "pixelsizey" in geometry.keys():
self.__settings.pixelsizey = float(geometry["pixelsizey"])
except Exception:
pass
try:
if "detdistance" in geometry.keys():
self.__settings.detdistance = float(geometry["detdistance"])
self._mainwidget.writeAttribute(
"DetectorDistance",
float(self.__settings.detdistance))
except Exception:
pass
if geometry:
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
if self.__plotindex:
self._mainwidget.emitReplotImage()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot(int)
def _setGSpaceIndex(self, gindex):
""" set gspace index
:param gspace: g-space index, i.e. angle or q-space
:type gspace: :obj:`int`
"""
self.__gspaceindex = gindex
[docs] @QtCore.pyqtSlot()
def updateGeometryTip(self):
""" update geometry tips
"""
message = self.__tipmessage()
self._mainwidget.updateDisplayedTextTip(
"coordinate info display for the mouse pointer\n%s"
% message)
self.__ui.angleqPushButton.setToolTip(
"Input physical parameters\n%s" % message)
self.__ui.angleqComboBox.setToolTip(
"Select the display space\n%s" % message)
[docs]class QROIProjToolWidget(ToolBaseWidget):
""" angle/q +roi + projections tool widget
"""
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) apply ROI pressed signal
applyROIPressed = QtCore.pyqtSignal(str, int)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) fetch ROI pressed signal
fetchROIPressed = QtCore.pyqtSignal(str)
#: (:class:`pyqtgraph.QtCore.pyqtSignal`) roi info Changed signal
roiInfoChanged = QtCore.pyqtSignal(str)
#: (:obj:`str`) tool name
name = "Q+ROI+Proj"
#: (:obj:`str`) tool name alias
alias = "q+roi+proj"
#: (:obj:`tuple` <:obj:`str`>) capitalized required packages
requires = ()
def __init__(self, parent=None):
""" constructor
:param parent: parent object
:type parent: :class:`pyqtgraph.QtCore.QObject`
"""
ToolBaseWidget.__init__(self, parent)
#: (:obj:`int`) geometry space index -> 0: angle, 1 q-space
self.__gspaceindex = 0
#: (:class:`Ui_ROIToolWidget') ui_toolwidget object from qtdesigner
self.__ui = _qroiprojformclass()
self.__ui.setupUi(self)
#: (:class:`pyqtgraph.PlotDataItem`) 1D bottom plot
self.__bottomplot = None
#: (:class:`pyqtgraph.PlotDataItem`) 1D bottom plot
self.__rightplot = None
#: (:obj:`int`) function index
self.__funindex = 0
#: (:obj:`slice`) selected rows
self.__rows = None
#: (:obj:`slice`) selected columns
self.__columns = None
#: (:obj:`slice`) selected rows
self.__dsrows = None
#: (:obj:`slice`) selected columns
self.__dscolumns = None
#: (:obj:`list`< :obj:`str`>) sardana aliases
self.__aliases = []
#: (:obj:`int`) ROI label length
self.__textlength = 0
self.parameters.bottomplot = True
self.parameters.rightplot = True
self.parameters.rois = True
self.parameters.infolineedit = ""
self.parameters.infotips = ""
self.parameters.centerlines = True
#: (:class:`lavuelib.settings.Settings`) configuration settings
self.__settings = self._mainwidget.settings()
self._updateApplyButton()
#: (:obj:`list` < [:class:`pyqtgraph.QtCore.pyqtSignal`, :obj:`str`] >)
#: list of [signal, slot] object to connect
self.signal2slot = [
[self.__ui.applyROIPushButton.clicked, self._emitApplyROIPressed],
[self.__ui.fetchROIPushButton.clicked, self._emitFetchROIPressed],
[self.__ui.angleqPushButton.clicked, self._setGeometry],
[self.__ui.angleqComboBox.currentIndexChanged,
self._setGSpaceIndex],
[self.__ui.angleqComboBox.currentIndexChanged,
self._mainwidget.emitTCC],
[self._mainwidget.mouseImageDoubleClicked,
self._updateCenter],
[self._mainwidget.mouseImagePositionChanged, self._message],
[self._mainwidget.geometryChanged, self.updateGeometryTip],
[self._mainwidget.geometryChanged, self._mainwidget.emitTCC],
[self.applyROIPressed, self._mainwidget.applyROIs],
[self.fetchROIPressed, self._mainwidget.fetchROIs],
[self.roiInfoChanged, self._mainwidget.updateDisplayedText],
[self.__ui.labelROILineEdit.textChanged,
self._updateApplyButton],
[self.__ui.roiSpinBox.valueChanged, self._mainwidget.updateROIs],
[self.__ui.roiSpinBox.valueChanged, self._mainwidget.emitTCC],
[self.__ui.roiSpinBox.valueChanged,
self._mainwidget.writeDetectorROIsAttribute],
[self.__ui.labelROILineEdit.textEdited,
self._writeDetectorROIs],
[self.__ui.labelROILineEdit.textEdited, self._mainwidget.emitTCC],
[self._mainwidget.roiLineEditChanged, self._updateApplyButton],
[self._mainwidget.roiAliasesChanged, self.updateROILineEdit],
[self._mainwidget.roiValueChanged, self.updateROIDisplayText],
[self._mainwidget.roiNumberChanged, self.setROIsNumber],
# [self._mainwidget.sardanaEnabled, self.updateROIButton],
[self._mainwidget.mouseImagePositionChanged, self._roimessage],
[self.__ui.funComboBox.currentIndexChanged,
self._setFunction],
[self.__ui.rowsliceLineEdit.textChanged, self._updateRows],
[self.__ui.rowsliceLineEdit.textChanged, self._mainwidget.emitTCC],
[self.__ui.columnsliceLineEdit.textChanged, self._updateColumns],
[self.__ui.columnsliceLineEdit.textChanged,
self._mainwidget.emitTCC],
[self._mainwidget.scalesChanged, self._updateRows],
[self._mainwidget.scalesChanged, self._updateColumns],
]
[docs] def configure(self, configuration):
""" set configuration for the current tool
:param configuration: configuration string
:type configuration: :obj:`str`
"""
if configuration:
cnf = json.loads(configuration)
if "geometry" in cnf.keys():
try:
self._updateGeometry(cnf["geometry"])
except Exception as e:
# print(str(e))
logger.warning(str(e))
if "rois_number" in cnf.keys():
try:
self.__ui.roiSpinBox.setValue(int(cnf["rois_number"]))
except Exception as e:
logger.warning(str(e))
# print(str(e))
if "rois_coords" in cnf.keys():
self._mainwidget.updateROIs(
len(cnf["rois_coords"]), cnf["rois_coords"])
if "aliases" in cnf.keys():
aliases = cnf["aliases"]
if isinstance(aliases, list):
aliases = " ".join(aliases)
self.__ui.labelROILineEdit.setText(aliases)
if "units" in cnf.keys():
idxs = ["angles", "q-space"]
xcrd = str(cnf["units"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.angleqComboBox.setCurrentIndex(idx)
if "apply" in cnf.keys():
if cnf["apply"]:
self._emitApplyROIPressed()
if "fetch" in cnf.keys():
if cnf["fetch"]:
self._emitFetchROIPressed()
if "mapping" in cnf.keys():
idxs = ["mean", "sum"]
xcrd = str(cnf["mapping"]).lower()
try:
idx = idxs.index(xcrd)
except Exception:
idx = 0
self.__ui.funComboBox.setCurrentIndex(idx)
if "rows" in cnf.keys():
self.__ui.rowsliceLineEdit.setText(cnf["rows"])
if "columns" in cnf.keys():
self.__ui.columnsliceLineEdit.setText(cnf["columns"])
[docs] def configuration(self):
""" provides configuration for the current tool
:returns configuration: configuration string
:rtype configuration: :obj:`str`
"""
cnf = {}
cnf["aliases"] = str(self.__ui.labelROILineEdit.text()).split(" ")
cnf["rois_number"] = self.__ui.roiSpinBox.value()
cnf["rois_coords"] = self._mainwidget.roiCoords()
cnf["mapping"] = str(
self.__ui.funComboBox.currentText()).lower()
cnf["rows"] = self.__ui.rowsliceLineEdit.text()
cnf["columns"] = self.__ui.columnsliceLineEdit.text()
cnf["units"] = str(
self.__ui.angleqComboBox.currentText()).lower()
cnf["geometry"] = {
"centerx": self.__settings.centerx,
"centery": self.__settings.centery,
"energy": self.__settings.energy,
"pixelsizex": self.__settings.pixelsizex,
"pixelsizey": self.__settings.pixelsizey,
"detdistance": self.__settings.detdistance,
}
return json.dumps(cnf, cls=numpyEncoder)
[docs] def activate(self):
""" activates tool widget
"""
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
self._mainwidget.changeROIRegion()
self.setROIsNumber(len(self._mainwidget.roiCoords()))
self.__aliases = self._mainwidget.getElementNames("ExpChannelList")
self.updateROILineEdit(self._mainwidget.roilabels)
self.__updateCompleter()
# self.updateROIButton(
# self.__settings.sardana or bool(self.__settings.analysisdevice))
if self.__bottomplot is None:
self.__bottomplot = self._mainwidget.onedbarbottomplot()
if self.__rightplot is None:
self.__rightplot = self._mainwidget.onedbarrightplot()
self.__bottomplot.show()
self.__rightplot.show()
self.__bottomplot.setVisible(True)
self.__rightplot.setVisible(True)
self._updateSlices()
self._plotCurves()
[docs] def deactivate(self):
""" deactivates tool widget
"""
self._mainwidget.roiCoordsChanged.emit()
if self.__bottomplot is not None:
self.__bottomplot.hide()
self.__bottomplot.setVisible(False)
self._mainwidget.removebottomplot(self.__bottomplot)
self.__bottomplot = None
if self.__rightplot is not None:
self.__rightplot.hide()
self.__rightplot.setVisible(False)
self._mainwidget.removerightplot(self.__rightplot)
self.__rightplot = None
@QtCore.pyqtSlot()
def _writeDetectorROIs(self):
""" writes Detector rois and updates roi labels
"""
self._mainwidget.roilabels = str(self.__ui.labelROILineEdit.text())
self._mainwidget.writeDetectorROIsAttribute()
def __updateslice(self, text, dx=None, ds=None):
""" create slices from the text
"""
rows = "ERROR"
dsrows = "ERROR"
if text:
try:
if ":" in text:
slices = text.split(":")
s0 = int(slices[0]) if slices[0].strip() else 0
s1 = int(slices[1]) if slices[1].strip() else None
if len(slices) > 2:
s2 = int(slices[2]) if slices[2].strip() else None
rows = slice(s0, s1, s2)
if dx is not None:
dsrows = slice((s0-dx)/ds, (s1-dx)/ds, s2/ds)
else:
dsrows = rows
else:
rows = slice(s0, s1)
if dx is not None:
dsrows = slice((s0-dx)/ds, (s1-dx)/ds)
else:
dsrows = rows
else:
rows = int(text)
if dx is not None:
dsrows = int((rows - dx)/ds)
else:
dsrows = rows
except Exception:
pass
else:
rows = None
dsrows = None
return rows, dsrows
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateSlices(self):
""" updates applied button"""
rtext = str(self.__ui.rowsliceLineEdit.text()).strip()
ctext = str(self.__ui.columnsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
self.__rows, self.__dsrows = self.__updateslice(
rtext, int(dy), int(ds2))
self.__columns, self.__dscolumns = self.__updateslice(
ctext, int(dx), int(ds1))
else:
self.__rows, self.__dsrows = self.__updateslice(rtext)
self.__columns, self.__dscolumns = self.__updateslice(ctext)
if self.__rows is None:
self._mainwidget.updateHBounds(None, None)
elif isinstance(self.__rows, int):
self._mainwidget.updateHBounds(self.__rows, self.__rows + 1)
elif isinstance(self.__rows, slice):
self._mainwidget.updateHBounds(self.__rows.start, self.__rows.stop)
if self.__columns is None:
self._mainwidget.updateVBounds(None, None)
elif isinstance(self.__columns, int):
self._mainwidget.updateVBounds(self.__columns, self.__columns + 1)
elif isinstance(self.__columns, slice):
self._mainwidget.updateVBounds(
self.__columns.start, self.__columns.stop)
self._plotCurves()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateRows(self):
""" updates applied button"""
rtext = str(self.__ui.rowsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
self.__rows, self.__dsrows = self.__updateslice(
rtext, int(dy), int(ds2))
else:
self.__rows, self.__dsrows = self.__updateslice(rtext)
if self.__rows is None:
self._mainwidget.updateHBounds(None, None)
elif isinstance(self.__rows, int):
self._mainwidget.updateHBounds(self.__rows, self.__rows + 1)
elif isinstance(self.__rows, slice):
self._mainwidget.updateHBounds(self.__rows.start, self.__rows.stop)
self._plotCurves()
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateColumns(self):
""" updates applied button"""
text = str(self.__ui.columnsliceLineEdit.text()).strip()
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
dx, dy, ds1, ds2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if rwe:
self.__columns, self.__dscolumns = self.__updateslice(
text, int(dx), int(ds1))
else:
self.__columns, self.__dscolumns = self.__updateslice(text)
if self.__columns is None:
self._mainwidget.updateVBounds(None, None)
elif isinstance(self.__columns, int):
self._mainwidget.updateVBounds(self.__columns, self.__columns + 1)
elif isinstance(self.__columns, slice):
self._mainwidget.updateVBounds(
self.__columns.start, self.__columns.stop)
self._plotCurves()
@QtCore.pyqtSlot(int)
def _setFunction(self, findex):
""" set sum or mean function
:param findex: function index, i.e. 0:mean, 1:sum
:type findex: :obj:`int`
"""
self.__funindex = findex
self._plotCurves()
@QtCore.pyqtSlot()
def _plotCurves(self):
""" plots the current image in 1d plots
"""
if self._mainwidget.currentTool() == self.name:
dts = self._mainwidget.rawData()
if dts is not None:
while dts.ndim > 2:
dts = np.nanmean(dts, axis=2)
if dts is not None:
if self.__funindex:
npfun = np.nansum
else:
npfun = np.nanmean
if self.__dsrows == "ERROR":
sx = []
elif self.__dsrows is not None:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
if isinstance(self.__dsrows, slice):
sx = npfun(dts[:, self.__dsrows], axis=1)
else:
sx = dts[:, self.__dsrows]
except Exception:
sx = []
else:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
sx = npfun(dts, axis=1)
if self.__dscolumns == "ERROR":
sy = []
if self.__dscolumns is not None:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
if isinstance(self.__dscolumns, slice):
sy = npfun(dts[self.__dscolumns, :], axis=0)
else:
sy = dts[self.__dscolumns, :]
except Exception:
sy = []
else:
try:
with np.warnings.catch_warnings():
np.warnings.filterwarnings(
"ignore", r'Mean of empty slice')
sy = npfun(dts, axis=0)
except Exception:
sy = []
rwe = self._mainwidget.rangeWindowEnabled()
if rwe:
x, y, s1, s2 = self._mainwidget.scale(
useraxes=False, noNone=True)
if self._mainwidget.transformations()[3]:
x, y = y, x
s1, s2 = s2, s1
xx = list(
range(int(x), len(sx) * int(s1) + int(x), int(s1)))
yy = list(
range(int(y), len(sy) * int(s2) + int(y), int(s2)))
else:
s1 = 1.0
s2 = 1.0
xx = list(range(len(sx)))
yy = list(range(len(sy)))
width = [s1] * len(sx)
height = [s2] * len(sy)
self.__bottomplot.setOpts(
y0=[0]*len(sx), y1=sx, x=xx,
width=width)
self.__bottomplot.drawPicture()
self.__rightplot.setOpts(
x0=[0]*len(sy), x1=sy, y=yy,
height=height)
self.__rightplot.drawPicture()
if self.__settings.sendresults:
xslice = self.__dsrows
yslice = self.__dscolumns
if hasattr(xslice, "start"):
xslice = [xslice.start, xslice.stop, xslice.step]
if hasattr(yslice, "start"):
yslice = [yslice.start, yslice.stop, yslice.step]
self.__sendresults(
xx,
[float(e) for e in sx],
s1, xslice,
yy,
[float(e) for e in sy],
s2, yslice,
"sum" if self.__funindex else "mean"
)
def __sendresults(self, xx, sx, xscale, xslice,
yy, sy, yscale, yslice, fun):
""" send results to LavueController
:param xx: x's coordinates
:type xx: :obj:`list` <float>
:param sx: projection to x coordinate
:type sx: :obj:`list` <float>
:param xscale: x scale
:type xscale: :obj:`float`
:param xslice: x slice
:type xslice: :obj:`list` <float>
:param yy: y's coordinates
:type yy: :obj:`list` <float>
:param sy: projection to y coordinate
:type sy: :obj:`list` <float>
:param yscale: y scale
:type yscale: :obj:`float`
:param yslice: y slice
:type yslice: :obj:`list` <float>
:param fun: projection function name
:type fun: :obj:`str`
"""
results = {"tool": self.alias}
results["imagename"] = self._mainwidget.imageName()
results["timestamp"] = time.time()
results["xx"] = xx
results["sx"] = sx
results["xscale"] = xscale
results["xslice"] = xslice
results["yy"] = yy
results["sy"] = sy
results["yscale"] = yscale
results["yslice"] = yslice
results["function"] = fun
self._mainwidget.writeAttribute(
"ToolResults", json.dumps(results, cls=numpyEncoder))
def __updateCompleter(self):
""" updates the labelROI help
"""
text = str(self.__ui.labelROILineEdit.text())
sttext = text.strip()
sptext = sttext.split()
stext = ""
if text.endswith(" "):
stext = sttext
elif len(sptext) > 1:
stext = " ".join(sptext[:-1])
if stext:
if self.__aliases:
hints = ["%s %s" % (stext, al) for al in self.__aliases]
else:
hints = [stext]
else:
hints = self.__aliases or []
completer = QtWidgets.QCompleter(hints, self)
self.__ui.labelROILineEdit.setCompleter(completer)
@QtCore.pyqtSlot()
def _roimessage(self):
""" provides roi message
"""
message = ""
current = self._mainwidget.currentROI()
coords = self._mainwidget.roiCoords()
if current > -1 and current < len(coords):
message = "%s" % coords[current]
self.__setDisplayedText(message)
def __setDisplayedText(self, text=None):
""" sets displayed info text and recalculates the current roi sum
:param text: text to display
:type text: :obj:`str`
"""
sroiVal = ""
if text is not None:
self.__lasttext = text
else:
text = self.__lasttext
if self.__settings.showallrois:
currentroi = self._mainwidget.currentROI()
roiVals = self._mainwidget.calcROIsums()
if roiVals is not None:
sroiVal = " / ".join(
[(("%g" % roiv) if roiv is not None else "?")
for roiv in roiVals])
else:
roiVal, currentroi = self._mainwidget.calcROIsum()
if roiVal is not None:
sroiVal = "%.4f" % roiVal
if currentroi is not None:
self.updateROIDisplayText(text, currentroi, sroiVal)
else:
self.__ui.roiinfoLineEdit.setText(text)
@QtCore.pyqtSlot()
def _emitApplyROIPressed(self):
""" emits applyROIPressed signal"""
text = str(self.__ui.labelROILineEdit.text())
roispin = int(self.__ui.roiSpinBox.value())
self.applyROIPressed.emit(text, roispin)
@QtCore.pyqtSlot()
def _emitFetchROIPressed(self):
""" emits fetchROIPressed signal"""
text = str(self.__ui.labelROILineEdit.text())
self.fetchROIPressed.emit(text)
@QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def _updateApplyButton(self):
""" updates applied button"""
stext = str(self.__ui.labelROILineEdit.text())
self._mainwidget.roilabels = stext
currentlength = len(stext)
if not stext.strip():
self.__ui.applyROIPushButton.setEnabled(False)
self.__updateCompleter()
else:
self.__ui.applyROIPushButton.setEnabled(True)
if stext.endswith(" ") or currentlength < self.__textlength:
self.__updateCompleter()
self.__textlength = currentlength
[docs] @QtCore.pyqtSlot(str)
def updateROILineEdit(self, text):
""" updates ROI line edit text
:param text: text to update
:type text: :obj:`str`
"""
if not self.__ui.labelROILineEdit.hasFocus():
self.__ui.labelROILineEdit.setText(text)
self._updateApplyButton()
[docs] @QtCore.pyqtSlot(bool)
def updateROIButton(self, enabled):
""" enables/disables ROI buttons
:param enable: buttons enabled
:type enable: :obj:`bool`
"""
# self.__ui.applyROIPushButton.setEnabled(enabled)
# self.__ui.fetchROIPushButton.setEnabled(enabled)
[docs] @QtCore.pyqtSlot(int)
def updateApplyTips(self, rid):
""" updates apply tips
:param rid: current roi id
:type rid: :obj:`int`
"""
if rid < 0:
self.__ui.applyROIPushButton.setToolTip(
"remove ROI aliases from the Door environment"
" as well as from Active MntGrp")
else:
self.__ui.applyROIPushButton.setToolTip(
"add ROI aliases to the Door environment "
"as well as to Active MntGrp")
[docs] @QtCore.pyqtSlot(str, int, str)
def updateROIDisplayText(self, text, currentroi, roiVal):
""" updates ROI display text
:param text: standard display text
:type text: :obj:`str`
:param currentroi: current roi label
:type currentroi: :obj:`str`
:param text: roi sum value
:type text: :obj:`str`
"""
roilabel = "roi [%s]" % (currentroi + 1)
slabel = []
rlabel = str(self.__ui.labelROILineEdit.text())
if rlabel:
slabel = re.split(';|,| |\n', rlabel)
slabel = [lb for lb in slabel if lb]
if slabel:
roilabel = "%s [%s]" % (
slabel[currentroi]
if currentroi < len(slabel) else slabel[-1],
(currentroi + 1)
)
if "/" in roiVal:
self.__ui.roiinfoLineEdit.setText(
"%s, %s; values = %s" % (text, roilabel, roiVal))
else:
self.__ui.roiinfoLineEdit.setText(
"%s, %s = %s" % (text, roilabel, roiVal))
[docs] @QtCore.pyqtSlot(int)
def setROIsNumber(self, rid):
"""sets a number of rois
:param rid: number of rois
:type rid: :obj:`int`
"""
self.__ui.roiSpinBox.setValue(rid)
# self._mainwidget.writeDetectorROIsAttribute()
@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`
"""
txdata = None
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
xdata, ydata, useraxes=False)
if txdata is not None:
xdata = txdata
ydata = tydata
self.__settings.centerx = float(xdata)
self.__settings.centery = float(ydata)
self._mainwidget.writeAttribute("BeamCenterX", float(xdata))
self._mainwidget.writeAttribute("BeamCenterY", float(ydata))
self._message()
self.updateGeometryTip()
self._mainwidget.emitTCC()
@QtCore.pyqtSlot()
def _message(self):
""" provides geometry message
"""
message = ""
_, _, intensity, x, y = self._mainwidget.currentIntensity()
if isinstance(intensity, float) and np.isnan(intensity):
intensity = 0
if isinstance(intensity, np.ndarray):
intensity = np.nansum(
[0 if (isinstance(it, float) and np.isnan(it))
else it for it in intensity])
if self._mainwidget.rangeWindowEnabled():
txdata, tydata = self._mainwidget.scaledxy(
x, y, useraxes=False)
if txdata is not None:
x = txdata
y = tydata
ilabel = self._mainwidget.scalingLabel()
if self.__gspaceindex == 0:
thetax, thetay, thetatotal = self.__pixel2theta(x, y)
if thetax is not None:
message = "th_x = %f deg, th_y = %f deg," \
" th_tot = %f deg, %s = %.2f" \
% (thetax * 180 / math.pi,
thetay * 180 / math.pi,
thetatotal * 180 / math.pi,
ilabel, intensity)
else:
qx, qy, q = self.__pixel2q(x, y)
if qx is not None:
message = u"q_x = %f 1/\u212B, q_y = %f 1/\u212B, " \
u"q = %f 1/\u212B, %s = %.2f" \
% (qx, qy, q, ilabel, intensity)
self._mainwidget.updateDisplayedText(message)
def __pixel2theta(self, xdata, ydata):
""" converts coordinates from pixel positions to theta angles
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:returns: x-theta, y-theta, total-theta
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
thetax = None
thetay = None
thetatotal = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
xcentered = xdata - self.__settings.centerx
ycentered = ydata - self.__settings.centery
thetax = math.atan(
xcentered * self.__settings.pixelsizex / 1000.
/ self.__settings.detdistance)
thetay = math.atan(
ycentered * self.__settings.pixelsizey / 1000.
/ self.__settings.detdistance)
r = math.sqrt(
(xcentered * self.__settings.pixelsizex / 1000.) ** 2
+ (ycentered * self.__settings.pixelsizey / 1000.) ** 2)
thetatotal = math.atan(
r / self.__settings.detdistance)
return thetax, thetay, thetatotal
def __pixel2q(self, xdata, ydata):
""" converts coordinates from pixel positions to q-space coordinates
:param xdata: x pixel position
:type xdata: :obj:`float`
:param ydata: y-pixel position
:type ydata: :obj:`float`
:returns: q_x, q_y, q_total
:rtype: (:obj:`float`, :obj:`float`, :obj:`float`)
"""
qx = None
qy = None
q = None
if self.__settings.energy > 0 and self.__settings.detdistance > 0:
thetax, thetay, thetatotal = self.__pixel2theta(
xdata, ydata)
wavelength = 12398.4193 / self.__settings.energy
qx = 4 * math.pi / wavelength * math.sin(thetax/2.)
qy = 4 * math.pi / wavelength * math.sin(thetay/2.)
q = 4 * math.pi / wavelength * math.sin(thetatotal/2.)
return qx, qy, q
def __tipmessage(self):
""" provides geometry messate
:returns: geometry text
:rtype: :obj:`unicode`
"""
return u"geometry:\n" \
u" center = (%s, %s) pixels\n" \
u" pixel_size = (%s, %s) \u00B5m\n" \
u" detector_distance = %s mm\n" \
u" energy = %s eV" % (
self.__settings.centerx,
self.__settings.centery,
self.__settings.pixelsizex,
self.__settings.pixelsizey,
self.__settings.detdistance,
self.__settings.energy
)
@QtCore.pyqtSlot()
def _setGeometry(self):
""" launches geometry widget
:returns: apply status
:rtype: :obj:`bool`
"""
cnfdlg = geometryDialog.GeometryDialog()
cnfdlg.centerx = self.__settings.centerx
cnfdlg.centery = self.__settings.centery
cnfdlg.energy = self.__settings.energy
cnfdlg.pixelsizex = self.__settings.pixelsizex
cnfdlg.pixelsizey = self.__settings.pixelsizey
cnfdlg.detdistance = self.__settings.detdistance
cnfdlg.createGUI()
if cnfdlg.exec_():
self.__settings.centerx = cnfdlg.centerx
self.__settings.centery = cnfdlg.centery
self.__settings.energy = cnfdlg.energy
self.__settings.pixelsizex = cnfdlg.pixelsizex
self.__settings.pixelsizey = cnfdlg.pixelsizey
self.__settings.detdistance = cnfdlg.detdistance
self.__settings.updateAISettings()
self._mainwidget.writeDetectorAttributes()
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
# @debugmethod
@QtCore.pyqtSlot()
def _updateGeometry(self, geometry):
""" update geometry widget
:param geometry: geometry dictionary
:type geometry: :obj:`dict` < :obj:`str`, :obj:`list`>
"""
try:
if "centerx" in geometry.keys():
self.__settings.centerx = float(geometry["centerx"])
self._mainwidget.writeAttribute(
"BeamCenterX", float(self.__settings.centerx))
except Exception:
pass
try:
if "centery" in geometry.keys():
self.__settings.centery = float(geometry["centery"])
self._mainwidget.writeAttribute(
"BeamCenterY", float(self.__settings.centery))
except Exception:
pass
try:
if "energy" in geometry.keys():
self.__settings.energy = float(geometry["energy"])
self._mainwidget.writeAttribute(
"Energy", float(self.__settings.energy))
except Exception:
pass
try:
if "pixelsizex" in geometry.keys():
self.__settings.pixelsizex = float(geometry["pixelsizex"])
except Exception:
pass
try:
if "pixelsizey" in geometry.keys():
self.__settings.pixelsizey = float(geometry["pixelsizey"])
except Exception:
pass
try:
if "detdistance" in geometry.keys():
self.__settings.detdistance = float(geometry["detdistance"])
self._mainwidget.writeAttribute(
"DetectorDistance",
float(self.__settings.detdistance))
except Exception:
pass
if geometry:
self.updateGeometryTip()
self._mainwidget.updateCenter(
self.__settings.centerx, self.__settings.centery)
self._mainwidget.emitTCC()
@QtCore.pyqtSlot(int)
def _setGSpaceIndex(self, gindex):
""" set gspace index
:param gspace: g-space index, i.e. angle or q-space
:type gspace: :obj:`int`
"""
self.__gspaceindex = gindex
[docs] @QtCore.pyqtSlot()
def updateGeometryTip(self):
""" update geometry tips
"""
message = self.__tipmessage()
self._mainwidget.updateDisplayedTextTip(
"coordinate info display for the mouse pointer\n%s"
% message)
self.__ui.angleqPushButton.setToolTip(
"Input physical parameters\n%s" % message)
self.__ui.angleqComboBox.setToolTip(
"Select the display space\n%s" % message)
self.__ui.toolLabel.setToolTip(
"coordinate info display for the mouse pointer\n%s" % message)
#: ( :obj:`dict` < :obj:`str`, any > ) tool widget properties
twproperties = []
for nm in __all__:
if nm.endswith("ToolWidget"):
cl = globals()[nm]
twproperties.append(
{
'alias': cl.alias,
'name': cl.name,
'widget': nm,
'requires': cl.requires,
})