Source code for easyleed.gui
"""
easyleed.gui
-------------
Various classes for providing a graphical user interface.
"""
import webbrowser
import pickle
import six
import time
import os.path
from .qt import get_qt_binding_name, qt_filedialog_convert
from .qt.QtCore import (QPoint, QRectF, QPointF, Qt, QTimer, QObject)
from .qt.QtCore import pyqtSignal as Signal
from .qt.widgets import (QApplication, QMainWindow, QGraphicsView, QGraphicsScene,
QWidget, QHBoxLayout, QGraphicsEllipseItem, QGraphicsRectItem,
QGraphicsItem,QGraphicsSimpleTextItem, QSlider, QVBoxLayout,
QPushButton, QToolButton, QAction, QFileDialog, QProgressBar,
QAbstractSlider, QFrame, QLabel, QRadioButton, QGridLayout,
QSpinBox, QDoubleSpinBox, QCheckBox, QComboBox, QLineEdit, QMessageBox)
from .qt.QtGui import (QImage, QPen, QIcon, QTransform, QImageWriter,
QPainter, QBrush, QKeySequence, QPixmap)
from . import config
from . import __version__
from . import __author__
from .base import *
from .io import *
import numpy as np
import pandas as pd
from scipy import interpolate
from matplotlib.figure import Figure
if get_qt_binding_name() == 'pyqt5':
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
else:
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4 import NavigationToolbar2QT
[docs]class QGraphicsMovableItem(QGraphicsItem):
""" Provides an QGraphicsItem that can be moved with the arrow keys.
Pressing Shift at the same time allows fine adjustments. """
def __init__(self, parent=None):
super(QGraphicsMovableItem, self).__init__(parent)
self.setFlags(QGraphicsItem.ItemIsMovable)
[docs] def keyPressEvent(self, event):
""" Handles keyPressEvents.
The item can be moved using the arrow keys. Applying Shift
at the same time allows fine adjustments.
"""
if event.key() == Qt.Key_Right:
if event.modifiers() & Qt.ShiftModifier:
self.moveRight(config.QGraphicsMovableItem_smallMove)
else:
self.moveRight(config.QGraphicsMovableItem_bigMove)
elif event.key() == Qt.Key_Left:
if event.modifiers() & Qt.ShiftModifier:
self.moveLeft(config.QGraphicsMovableItem_smallMove)
else:
self.moveLeft(config.QGraphicsMovableItem_bigMove)
elif event.key() == Qt.Key_Up:
if event.modifiers() & Qt.ShiftModifier:
self.moveUp(config.QGraphicsMovableItem_smallMove)
else:
self.moveUp(config.QGraphicsMovableItem_bigMove)
elif event.key() == Qt.Key_Down:
if event.modifiers() & Qt.ShiftModifier:
self.moveDown(config.QGraphicsMovableItem_smallMove)
else:
self.moveDown(config.QGraphicsMovableItem_bigMove)
[docs] def moveRight(self, distance):
""" Moves the circle distance to the right."""
self.setPos(self.pos() + QPointF(distance, 0.0))
[docs] def moveLeft(self, distance):
""" Moves the circle distance to the left."""
self.setPos(self.pos() + QPointF(-distance, 0.0))
[docs] def moveUp(self, distance):
""" Moves the circle distance up."""
self.setPos(self.pos() + QPointF(0.0, -distance))
[docs] def moveDown(self, distance):
""" Moves the circle distance down."""
self.setPos(self.pos() + QPointF(0.0, distance))
[docs] def onPositionChange(self, point):
""" Handles incoming position change request."""
self.setPos(point)
[docs]class QGraphicsSpotItem(QGraphicsEllipseItem, QGraphicsMovableItem):
""" Provides an QGraphicsItem to display a Spot on a QGraphicsScene. """
def __init__(self, point, radius, parent=None):
super(QGraphicsSpotItem, self).__init__(parent)
offset = QPointF(radius, radius)
self.setRect(QRectF(-offset, offset))
self.setPen(QPen(Qt.blue))
self.setPos(point)
self.setFlags(self.flags() |
QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsFocusable)
[docs] def keyPressEvent(self, event):
""" Handles keyPressEvents.
The circles radius can be changed using the plus and minus keys.
"""
if event.key() == Qt.Key_Plus:
self.changeSize(config.QGraphicsSpotItem_spotSizeChange)
elif event.key() == Qt.Key_Minus:
self.changeSize(-config.QGraphicsSpotItem_spotSizeChange)
else:
super(QGraphicsSpotItem, self).keyPressEvent(event)
[docs] def onRadiusChange(self, radius):
""" Handles incoming radius change request."""
self.changeSize(radius - self.radius())
def radius(self):
return self.rect().width() / 2.0
[docs] def changeSize(self, inc):
""" Change radius by inc.
inc > 0: increase
inc < 0: decrease
"""
inc /= 2**0.5
self.setRect(self.rect().adjusted(-inc, -inc, +inc, +inc))
[docs]class QGraphicsCenterItem(QGraphicsRectItem, QGraphicsMovableItem):
""" Provides an QGraphicsItem to display the center position on a QGraphicsScene. """
def __init__(self, point, size, parent=None):
super(QGraphicsCenterItem, self).__init__(parent)
offset = QPointF(size, size)
self.setRect(QRectF(-offset, offset))
self.setPen(QPen(Qt.red))
self.setPos(point)
self.setFlags(self.flags() |
QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsFocusable)
[docs]class QSpotModel(QObject):
""" Wraps a SpotModel to offer signals.
Provides the following signals:
- intensityChanged
- positionChanged
- radiusChanged
"""
intensityChanged = Signal(object)
positionChanged = Signal(object)
radiusChanged = Signal(object)
def __init__(self, parent=None):
super(QSpotModel, self).__init__(parent)
self.m = SpotModel()
def update(self, x, y, intensity, energy, radius):
self.m.update(x, y, intensity, energy, radius)
self.intensityChanged.emit(intensity)
self.positionChanged.emit(QPointF(x, y))
self.radiusChanged.emit(radius)
[docs]class GraphicsScene(QGraphicsScene):
""" Custom GraphicScene having all the main content."""
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(parent)
self.spots = []
self.center = None
self.spotsLabel = []
def addSpot(self, item):
self.clearSelection()
self.addItem(item)
item.setSelected(True)
self.setFocusItem(item)
self.spots.append(item)
self.spotsLabel.append(str(len(self.spots)))
item.setToolTip(self.spotsLabel[-1])
[docs] def mousePressEvent(self, event):
""" Processes mouse events through either
- propagating the event
or
- instantiating a new Circle (on left-click)
- instantiating a new Center (on right-click)
"""
transform = QTransform()
if hasattr(self, "image"):
if self.itemAt(event.scenePos(), transform):
super(GraphicsScene, self).mousePressEvent(event)
elif event.button() == Qt.LeftButton:
if self.parent().paused == False:
item = QGraphicsSpotItem(event.scenePos(),
config.GraphicsScene_defaultRadius)
self.addSpot(item)
# Enable spots to be saved when present on the image
#if len(self.spots) > 0:
# self.parent().fileSaveSpotsAction.setEnabled(True)
elif event.button() == Qt.RightButton:
if self.center is None:
item = QGraphicsCenterItem(event.scenePos(),
config.QGraphicsCenterItem_size)
self.clearSelection()
self.addItem(item)
item.setSelected(True)
self.setFocusItem(item)
self.center = item
self.parent().fileSaveCenterAction.setEnabled(True)
else:
print("failure: center already defined")
else:
self.parent().statusBar().showMessage("Spots require a loaded image", 5000)
[docs] def keyPressEvent(self, event):
""" Processes key events through either
- deleting the focus item
or
- propagating the event
"""
item = self.focusItem()
if item:
if event.key() == Qt.Key_Delete:
if type(item) is QGraphicsSpotItem:
self.spots.remove(item)
self.removeItem(item)
try:
line = self.parent().plotwid.lines_map[item]
line.remove()
self.parent().plotwid.updatePlot()
except:
pass
elif type(item) is QGraphicsCenterItem:
self.removeCenter()
del item
else:
super(GraphicsScene, self).keyPressEvent(event)
[docs] def drawBackground(self, painter, rect):
""" Draws image in background if it exists. """
if hasattr(self, "image"):
painter.drawImage(QPoint(0, 0), self.image)
[docs] def setBackground(self, image, labeltext):
""" Sets the background image. """
if not hasattr(self, 'imlabel'):
self.imlabel = QGraphicsSimpleTextItem(labeltext)
self.imlabel.setBrush(QBrush(Qt.white))
self.imlabel.setPos(5, 5)
if not hasattr(self,"image") or len(self.items()) < 1:
self.addItem(self.imlabel)
self.imlabel.setText(labeltext)
self.image = image
self.update()
[docs] def removeAll(self):
""" Remove all spots from the scene (leaves background unchanged). """
for item in self.items():
if type(item) == QGraphicsSpotItem:
self.removeItem(item)
self.spots = []
self.removeCenter()
[docs] def removeCenter(self):
""" Remove center from the scene (leaves background unchanged). """
if self.center is not None:
center = self.center
self.removeItem(center)
del center
self.center = None
[docs]class GraphicsView(QGraphicsView):
""" Custom GraphicsView to display the scene. """
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setRenderHints(QPainter.Antialiasing)
def resizeEvent(self, event):
self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
def drawBackground(self, painter, rect):
painter.fillRect(rect, QBrush(Qt.lightGray))
self.scene().drawBackground(painter, rect)
[docs]class AboutWidget(QWidget):
""" PyQt widget for About Box Panel """
def __init__(self):
super(AboutWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(100, 200, 400, 200)
self.setWindowTitle('About EasyLEED')
self.gridLayout = QGridLayout()
self.setLayout(self.gridLayout)
self.verticalLayout = QVBoxLayout()
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
self.labelTitle = QLabel("<qt><b><big><a href = http://andim.github.io/easyleed/index.html>EasyLEED %s</a></b></big></qt>" % __version__, self);
self.labelBy = QLabel("by: %s" % __author__, self)
self.labelContact = QLabel("<qt>Contacts: <a href = mailto:andimscience@gmail.com>andimscience@gmail.com</a>, <a href = mailto:feranick@hotmail.com> feranick@hotmail.com</a></qt>", self)
self.labelDetails = QLabel("If EasyLEED has been useful in your research please cite: ", self)
self.labelPaper = QLabel("<qt><a href = http://dx.doi.org/10.1016/j.cpc.2012.02.019>A Mayer, H Salopaasi, K Pussi, RD Diehl. Comput. Phys. Commun. 183, 1443-1447 (2012)</a>", self)
for label in [self.labelTitle, self.labelBy, self.labelContact, self.labelDetails, self.labelPaper]:
label.setWordWrap(True)
label.setOpenExternalLinks(True);
self.verticalLayout.addWidget(label)
class CustomPlotToolbar(NavigationToolbar2QT):
# only display the buttons we need
toolitems = [t for t in NavigationToolbar2QT.toolitems if
t[0] in ('Home', None,'Pan','Zoom','Subplots','Save')]
def __init__(self, *args, **kwargs):
super(CustomPlotToolbar, self).__init__(*args, **kwargs)
self.layout().takeAt(1) #or more than 1 if you have more buttons
self.setStyleSheet("QToolBar { border: 0px }")
[docs]class PlotWidget(QWidget):
""" Custom PyQt widget canvas for plotting """
def __init__(self):
super(PlotWidget, self).__init__()
self.setWindowTitle("I(E)-curve")
self.create_main_frame()
[docs] def create_main_frame(self):
""" Create the mpl Figure and FigCanvas objects. """
# 5x4 inches, 100 dots-per-inch
self.setGeometry(700, 420, 600, 500)
self.dpi = 100
self.fig = Figure((5.0, 4.0), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self)
# Create the navigation toolbar, tied to the canvas
self.mpl_toolbar = CustomPlotToolbar(self.canvas, self)
# Add checkbox for average
self.averageCheck = QCheckBox("Average")
self.averageCheck.setChecked(config.GraphicsScene_plotAverage)
# Add checkbox for smooth average
self.smoothCheck = QCheckBox("Smooth Average")
self.smoothCheck.setChecked(config.GraphicsScene_plotSmoothAverage)
# Add checkbox for hiding legend in plot
self.legendCheck = QCheckBox("Show Legend")
self.legendCheck.setChecked(False)
# Add cButton for clearing plot
self.clearPlotButton = QPushButton('&Clear Plot', self)
# Layout
self.gridLayout = QGridLayout()
self.setLayout(self.gridLayout)
self.gridLayout.addWidget(self.mpl_toolbar, 0, 0, 1, -1)
self.gridLayout.addWidget(self.canvas, 1, 0, 1, -1)
self.gridLayout.addWidget(self.averageCheck, 2, 0, 1, 1)
self.gridLayout.addWidget(self.smoothCheck, 3, 0, 1, 1)
self.gridLayout.addWidget(self.clearPlotButton, 2, 1, 1, 1)
self.gridLayout.addWidget(self.legendCheck, 3, 1, 1, 1)
# Define events for checkbox
self.averageCheck.clicked.connect(self.onAverageCheck)
for button in [self.smoothCheck, self.legendCheck]:
button.clicked.connect(self.updatePlot)
self.clearPlotButton.clicked.connect(self.clearPlot)
def initPlot(self):
# Setup axis, labels, lines, ...
if config.GraphicsScene_intensTimeOn:
self.axes.set_xlabel("Frame")
else:
self.axes.set_xlabel("Energy [eV]")
self.axes.set_ylabel("Intensity [a.u.]")
# removes the ticks from y-axis
self.axes.set_yticks([])
self.fig.tight_layout()
def setupPlot(self, worker):
self.initPlot()
self.worker = worker
self.lines_map = {}
for i, spot in enumerate(worker.parent().scene.spots, start=1):
self.lines_map[spot], = self.axes.plot([], [], label=str(i))
# show dashed line at y = 0
self.axes.axhline(0.0, color='k', ls='--')
# try to auto-adjust plot margins (might not be available in all matplotlib versions)
try:
self.fig.tight_layout()
except:
pass
self.updatePlot()
self.axes.legend(fontsize=10)
self.axes.legend().set_visible(self.legendCheck.isChecked())
self.show()
def onAverageCheck(self):
if self.averageCheck.isChecked():
self.smoothCheck.setEnabled(True)
else:
self.smoothCheck.setEnabled(False)
self.updatePlot()
[docs] def updatePlot(self):
""" Basic Matplotlib plotting I(E)-curve """
for spot, line in six.iteritems(self.lines_map):
line.set_data(self.worker.spots_map[spot][0].m.energy, self.worker.spots_map[spot][0].m.intensity)
if self.averageCheck.isChecked() and len(self.axes.lines) > 1:
intensity = np.zeros(self.worker.numProcessed())
for model, tracker in six.itervalues(self.worker.spots_map):
intensity += model.m.intensity
intensity /= len(self.worker.spots_map)
if hasattr(self, "averageLine"):
self.averageLine.set_data(model.m.energy, intensity)
else:
# set up averageLine
self.averageLine, = self.axes.plot(model.m.energy, intensity, 'k', lw=2, label='Average')
if self.smoothCheck.isChecked():
tck = interpolate.splrep(model.m.energy, intensity, s=config.GraphicsScene_smoothSpline)
xnew = np.arange(model.m.energy[0], model.m.energy[-1],
(model.m.energy[1]-model.m.energy[0])*config.GraphicsScene_smoothPoints)
ynew = interpolate.splev(xnew, tck, der=0)
if hasattr(self, "averageSmoothLine"):
self.averageSmoothLine.set_data(xnew, ynew)
else:
# set up averageSmoothLine
self.averageSmoothLine, = self.axes.plot(xnew, ynew, 'b', lw=2, label='Smooth Average')
else:
if hasattr(self, "averageSmoothLine"):
self.averageSmoothLine.remove()
del self.averageSmoothLine
else:
if hasattr(self, "averageLine"):
try:
self.averageLine.remove()
del self.averageLine
except:
pass
if self.axes.legend() is not None:
# decide whether to show legend
self.axes.legend().set_visible(self.legendCheck.isChecked())
# ... axes limits
self.axes.relim()
self.axes.autoscale_view(True, True, True)
# and show the new plot
self.canvas.draw()
def clearPlot(self):
self.averageCheck.setChecked(False)
self.updatePlot()
self.fig.clf()
self.axes = self.fig.add_subplot(111)
self.initPlot()
self.canvas.draw()
[docs] def save(self):
""" Saving the plot """
filename = "plot.png"
filename = qt_filedialog_convert(QFileDialog.getSaveFileName(self,
"Save the plot to a file",
filename))
if filename:
self.fig.savefig(filename)
[docs]class ParameterSettingWidget(QWidget):
"""PyQt widget for setting tracking parameters"""
def __init__(self):
super(ParameterSettingWidget, self).__init__()
self.initUI()
def initUI(self):
# Buttons/elements
self.inputPrecision = QSpinBox(self)
self.inputPrecision.setWrapping(True)
self.inputPrecision.setValue(config.Tracking_inputPrecision)
self.ipLabel = QLabel("User input precision", self)
self.inputPrecision.editingFinished.connect(self.collectParameters)
self.integrationWindowRadiusNew = QSpinBox(self)
self.integrationWindowRadiusNew.setWrapping(True)
self.integrationWindowRadiusNew.setValue(config.GraphicsScene_defaultRadius)
self.iwrnLabel = QLabel("Default radius of a new spot", self)
self.integrationWindowRadiusNew.editingFinished.connect(self.collectParameters)
self.integrationWindowRadius = QSpinBox(self)
self.integrationWindowRadius.setWrapping(True)
self.integrationWindowRadius.setValue(config.Tracking_minWindowSize)
self.iwrLabel = QLabel("Minimal radius of the integration window", self)
self.integrationWindowRadius.editingFinished.connect(self.collectParameters)
self.validationRegionSize = QSpinBox(self)
self.validationRegionSize.setWrapping(True)
self.validationRegionSize.setValue(config.Tracking_gamma)
self.vrsLabel = QLabel("Size of the validation region", self)
self.validationRegionSize.editingFinished.connect(self.collectParameters)
self.fitRegionFactor = QDoubleSpinBox(self)
self.fitRegionFactor.setWrapping(True)
self.fitRegionFactor.setSingleStep(0.01)
self.fitRegionFactor.setValue(config.Tracking_fitRegionFactor)
self.frfLabel = QLabel("Searching area for peaks", self)
self.fitRegionFactor.editingFinished.connect(self.collectParameters)
self.determinationCoefficient = QDoubleSpinBox(self)
self.determinationCoefficient.setWrapping(True)
self.determinationCoefficient.setSingleStep(0.01)
self.determinationCoefficient.setValue(config.Tracking_minRsq)
self.dcLabel = QLabel("Minimal R" + chr(0x00B2) + " to accept fit", self)
self.determinationCoefficient.editingFinished.connect(self.collectParameters)
self.integrationWindowScale = QCheckBox("Scale integration window with changing energy")
self.integrationWindowScale.setChecked(config.Tracking_windowScalingOn)
self.integrationWindowScale.stateChanged.connect(self.collectParameters)
self.backgroundSubstraction = QCheckBox("Background substraction")
self.backgroundSubstraction.setChecked(config.Processing_backgroundSubstractionOn)
self.backgroundSubstraction.stateChanged.connect(self.collectParameters)
self.livePlotting = QCheckBox("Plot I(E) intensities during acquisition")
self.livePlotting.setChecked(config.GraphicsScene_livePlottingOn)
self.livePlotting.stateChanged.connect(self.collectParameters)
self.intensTime = QCheckBox("Extract I(frame) - fixed energy")
self.intensTime.setChecked(config.GraphicsScene_intensTimeOn)
self.intensTime.stateChanged.connect(self.collectParameters)
self.smoothPoints = QSpinBox(self)
self.smoothPoints.setWrapping(True)
self.smoothPoints.setToolTip("Press Enter to update plot")
self.smoothPoints.setValue(config.GraphicsScene_smoothPoints)
self.smPoiLabel = QLabel("# points to be rescaled for smoothing", self)
self.smoothPoints.editingFinished.connect(self.collectParameters)
self.smoothSpline = QSpinBox(self)
self.smoothSpline.setWrapping(True)
self.smoothSpline.setToolTip("Press Enter to update plot")
self.smoothSpline.setValue(config.GraphicsScene_smoothSpline)
self.smSplLabel = QLabel("Amount of smoothing to perform", self)
self.smoothSpline.editingFinished.connect(self.collectParameters)
self.spotIdentification = QComboBox(self)
guesser_routines_l = list(guesser_routines)
for guesser in guesser_routines_l:
self.spotIdentification.addItem(guesser)
self.spotIdentification.setCurrentIndex(guesser_routines_l.index(config.Tracking_guessFunc))
self.siLabel = QLabel("Spot ident. algorithm", self)
self.spotIdentification.currentIndexChanged.connect(self.collectParameters)
self.fnLabel = QLabel("Kalman tracker process noise Q", self)
self.processNoisePosition = QDoubleSpinBox(self)
self.processNoisePosition.setSingleStep(0.1)
self.processNoisePosition.setValue(config.Tracking_processNoisePosition)
self.processNoisePositionLabel = QLabel("Position", self)
self.processNoisePosition.editingFinished.connect(self.collectParameters)
self.processNoiseVelocity = QDoubleSpinBox(self)
self.processNoiseVelocity.setSingleStep(0.1)
self.processNoiseVelocity.setValue(config.Tracking_processNoiseVelocity)
self.processNoiseVelocityLabel = QLabel("Velocity", self)
self.processNoiseVelocity.editingFinished.connect(self.collectParameters)
self.saveButton = QPushButton('&Save', self)
self.loadButton = QPushButton('&Load', self)
self.defaultButton = QPushButton('&Restore Default', self)
self.wrongLabel = QLabel(" ", self)
self.applyButton = QPushButton('&Save as Default', self)
self.vertLine = QFrame()
self.vertLine.setFrameStyle(QFrame.VLine)
self.horLine = QFrame()
self.horLine.setFrameStyle(QFrame.HLine)
self.horLine2 = QFrame()
self.horLine2.setFrameStyle(QFrame.HLine)
#Layouts
self.setGeometry(700, 30, 300, 100)
self.setWindowTitle('Set acquisition parameters')
#base grid
self.gridLayout = QGridLayout()
self.setLayout(self.gridLayout)
#vertical line layout
self.vlineLayout = QVBoxLayout()
self.vlineLayout.addWidget(self.vertLine)
#1st (left) vertical layout
self.lh1Layout = QHBoxLayout()
self.lh1Layout.addWidget(self.ipLabel)
self.lh1Layout.addWidget(self.inputPrecision)
self.lh2Layout = QHBoxLayout()
self.lh2Layout.addWidget(self.iwrnLabel)
self.lh2Layout.addWidget(self.integrationWindowRadiusNew)
self.lh3Layout = QHBoxLayout()
self.lh3Layout.addWidget(self.iwrLabel)
self.lh3Layout.addWidget(self.integrationWindowRadius)
self.lh4Layout = QHBoxLayout()
self.lh4Layout.addWidget(self.vrsLabel)
self.lh4Layout.addWidget(self.validationRegionSize)
self.lh5Layout = QHBoxLayout()
self.lh5Layout.addWidget(self.frfLabel)
self.lh5Layout.addWidget(self.fitRegionFactor)
self.lh6Layout = QHBoxLayout()
self.lh6Layout.addWidget(self.dcLabel)
self.lh6Layout.addWidget(self.determinationCoefficient)
self.lh7Layout = QHBoxLayout()
self.lh7Layout.addWidget(self.smPoiLabel)
self.lh7Layout.addWidget(self.smoothPoints)
self.lh8Layout = QHBoxLayout()
self.lh8Layout.addWidget(self.smSplLabel)
self.lh8Layout.addWidget(self.smoothSpline)
#2nd (right) vertical layout
self.rh1Layout = QHBoxLayout()
self.rh1Layout.addWidget(self.intensTime)
self.rh2Layout = QHBoxLayout()
self.rh2Layout.addWidget(self.integrationWindowScale)
self.rh3Layout = QHBoxLayout()
self.rh3Layout.addWidget(self.backgroundSubstraction)
self.rh4Layout = QHBoxLayout()
self.rh4Layout.addWidget(self.livePlotting)
self.rh5Layout = QHBoxLayout()
self.rh5Layout.addWidget(self.horLine2)
self.rh6Layout = QHBoxLayout()
self.rh6Layout.addWidget(self.siLabel)
self.rh6Layout.addWidget(self.spotIdentification)
self.rh7Layout = QHBoxLayout()
self.rh7Layout.addWidget(self.fnLabel)
self.rh8Layout = QHBoxLayout()
self.rh8Layout.addWidget(self.processNoisePositionLabel)
self.rh8Layout.addWidget(self.processNoisePosition)
self.rh9Layout = QHBoxLayout()
self.rh9Layout.addWidget(self.processNoiseVelocityLabel)
self.rh9Layout.addWidget(self.processNoiseVelocity)
#horizontal layout left
self.hLayout = QHBoxLayout()
self.hLayout.addWidget(self.loadButton)
self.hLayout.addWidget(self.saveButton)
self.hLayout.addWidget(self.defaultButton)
self.hLayout.addWidget(self.wrongLabel)
self.hLayout.addWidget(self.applyButton)
#adding layouts to the grid
self.gridLayout.addLayout(self.lh1Layout, 0, 0)
self.gridLayout.addLayout(self.lh2Layout, 1, 0)
self.gridLayout.addLayout(self.lh3Layout, 2, 0)
self.gridLayout.addLayout(self.lh4Layout, 3, 0)
self.gridLayout.addLayout(self.lh5Layout, 4, 0)
self.gridLayout.addLayout(self.lh6Layout, 5, 0)
self.gridLayout.addLayout(self.lh7Layout, 6, 0)
self.gridLayout.addLayout(self.lh8Layout, 7, 0)
self.gridLayout.addLayout(self.rh1Layout, 0, 2)
self.gridLayout.addLayout(self.rh2Layout, 1, 2)
self.gridLayout.addLayout(self.rh3Layout, 2, 2)
self.gridLayout.addLayout(self.rh4Layout, 3, 2)
self.gridLayout.addLayout(self.rh5Layout, 4, 2)
self.gridLayout.addLayout(self.rh6Layout, 5, 2)
self.gridLayout.addLayout(self.rh7Layout, 6, 2)
self.gridLayout.addLayout(self.rh8Layout, 7, 2)
self.gridLayout.addLayout(self.rh9Layout, 8, 2)
self.gridLayout.addLayout(self.hLayout, 8, 0)
self.gridLayout.addLayout(self.vlineLayout, 0, 1, 9, 1)
self.applyButton.clicked.connect(self.applyParameters)
self.defaultButton.clicked.connect(self.defaultValues)
self.saveButton.clicked.connect(self.saveValues)
self.loadButton.clicked.connect(self.loadValues)
[docs] def collectParameters(self):
"""Parameter setting control"""
config.GraphicsScene_defaultRadius = self.integrationWindowRadiusNew.value()
config.GraphicsScene_smoothPoints = self.smoothPoints.value()
config.GraphicsScene_smoothSpline = self.smoothSpline.value()
config.Tracking_inputPrecision = self.inputPrecision.value()
config.Tracking_minWindowSize = self.integrationWindowRadius.value()
config.Tracking_guessFunc = self.spotIdentification.currentText()
config.Tracking_processNoisePosition = self.processNoisePosition.value()
config.Tracking_processNoiseVelocity = self.processNoiseVelocity.value()
config.Tracking_gamma = self.validationRegionSize.value()
config.Tracking_minRsq = self.determinationCoefficient.value()
config.Tracking_fitRegionFactor = self.fitRegionFactor.value()
[docs] def applyParameters(self):
"""Parameter setting control"""
self.collectParameters()
config.GraphicsScene_livePlottingOn = self.livePlotting.isChecked()
config.GraphicsScene_intensTimeOn = self.intensTime.isChecked()
config.Tracking_windowScalingOn = self.integrationWindowScale.isChecked()
config.Processing_backgroundSubstractionOn = self.backgroundSubstraction.isChecked()
config.conf['GUI']['GraphicsScene_defaultRadius'] = str(config.GraphicsScene_defaultRadius)
config.conf['GUI']['GraphicsScene_livePlottingOn'] = str(config.GraphicsScene_livePlottingOn)
config.conf['GUI']['GraphicsScene_intensTimeOn'] = str(config.GraphicsScene_intensTimeOn)
config.conf['GUI']['GraphicsScene_smoothPoints'] = str(config.GraphicsScene_smoothPoints)
config.conf['GUI']['GraphicsScene_smoothSpline'] = str(config.GraphicsScene_smoothSpline)
config.conf['Tracking']['Tracking_inputPrecision'] = str(config.Tracking_inputPrecision)
config.conf['Tracking']['Tracking_windowScalingOn'] = str(config.Tracking_windowScalingOn)
config.conf['Tracking']['Tracking_minWindowSize'] = str(config.Tracking_minWindowSize)
config.conf['Tracking']['Tracking_guessFunc'] = str(config.Tracking_guessFunc)
config.conf['Tracking']['Tracking_processNoisePosition'] = str(config.Tracking_processNoisePosition)
config.conf['Tracking']['Tracking_processNoiseVelocity'] = str(config.Tracking_processNoiseVelocity)
config.conf['Tracking']['Tracking_gamma'] = str(config.Tracking_gamma)
config.conf['Tracking']['Tracking_minRsq'] = str(config.Tracking_minRsq)
config.conf['Tracking']['Tracking_fitRegionFactor'] = str(config.Tracking_fitRegionFactor)
config.conf['Processing']['Processing_backgroundSubstractionOn'] = str(config.Processing_backgroundSubstractionOn)
config.saveConfig(config.configFile)
logger.info("Current acquisition parameters set as default")
def defaultValues(self):
# Set default acquisition parameters from configuration ini
config.createConfig()
config.readConfig(config.configFile)
self.inputPrecision.setValue(config.Tracking_inputPrecision)
self.integrationWindowRadiusNew.setValue(config.GraphicsScene_defaultRadius)
self.integrationWindowRadius.setValue(config.Tracking_minWindowSize)
self.validationRegionSize.setValue(config.Tracking_gamma)
self.fitRegionFactor.setValue(config.Tracking_fitRegionFactor)
self.determinationCoefficient.setValue(config.Tracking_minRsq)
self.smoothPoints.setValue(config.GraphicsScene_smoothPoints)
self.smoothSpline.setValue(config.GraphicsScene_smoothSpline)
self.processNoisePosition.setValue(config.Tracking_processNoisePosition)
self.processNoiseVelocity.setValue(config.Tracking_processNoiseVelocity)
self.intensTime.setChecked(config.GraphicsScene_intensTimeOn)
self.integrationWindowScale.setChecked(config.Tracking_windowScalingOn)
self.backgroundSubstraction.setChecked(config.Processing_backgroundSubstractionOn)
self.livePlotting.setChecked(config.GraphicsScene_livePlottingOn)
logger.info("Default acquisition parameters restored")
[docs] def saveValues(self):
""" Basic saving of the set parameter values to a file """
filename = QFileDialog.getSaveFileName(self,
"Save INI config file", "","*.ini")
self.collectParameters()
config.saveConfig(filename[0])
print("Configuration parameters saved to:",filename[0])
logger.info("Configuration parameters saved to:"+filename[0])
[docs] def loadValues(self):
""" Load a file of set parameter values that has been saved with the widget """
filename = QFileDialog.getOpenFileName(self,
"Open INI config file", "","*.ini")
try:
config.readConfig(filename)
print("Configuration parameters loaded from:",filename[0])
logger.info("Configuration parameters loaded from:"+filename[0])
except:
print("Invalid file")
[docs]class MainWindow(QMainWindow):
""" EasyLEED's main window. """
sliderCurrentPos = 1
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle("EasyLEED %s" % __version__)
#### setup central widget ####
self.aboutwid = AboutWidget()
self.plotwid = PlotWidget()
self.parametersettingwid = ParameterSettingWidget()
self.scene = GraphicsScene(self)
self.view = GraphicsView()
self.view.setScene(self.scene)
self.view.setMinimumSize(660, 480)
self.setGeometry(10, 30, 660, 480)
self.setCentralWidget(self.view)
self.scene.selectionChanged.connect(self.highlightSelSpot)
self.paused = False
#### define actions ####
## actions for "Process" menu
self.processRunAction = self.createAction("&Run", self.run,
QKeySequence("Ctrl+r"), None,
"Run the analysis of the images.")
processStopAction = self.createAction("&Stop", self.stopProcessing,
QKeySequence("Ctrl+w"), None,
"Stop the analysis of the images.")
self.processPauseAction = self.createAction("&Pause", self.pauseProcessing,
QKeySequence("Ctrl+p"), None,
"Pause the analysis of the images.")
processRestartAction = self.createAction("&Restart", self.restart,
QKeySequence("Ctrl+z"), None,
"Reset chosen points and jump to first image.")
processNextAction = self.createAction("&Next Image", self.next_,
QKeySequence("Ctrl+right"), None,
"Open next image.")
processPreviousAction = self.createAction("&Previous Image", self.previous,
QKeySequence("Ctrl+left"), None,
"Open previous image.")
processPlotOptions = self.createAction("&Plot...", self.plot,
QKeySequence("Ctrl+Shift+p"), None,
"Plot Intensities.")
processSetParameters = self.createAction("&Set Parameters", self.parametersettingwid.show,
None, None,
"Set tracking parameters.")
self.processRemoveSpot = self.createAction("&Remove Spot", self.removeLastSpot,
QKeySequence("Ctrl+Shift+r"), None,
"Remove Last Spot.")
self.processActions = [processNextAction, processPreviousAction, None, self.processRunAction, self.processPauseAction, processStopAction, processRestartAction, None, processPlotOptions, None, self.processRemoveSpot]
# actions for "File" menu
self.fileOpenAction = self.createAction("&Open...", self.fileOpen,
QKeySequence.Open, None,
"Open a directory containing the image files.")
self.fileSaveAction = self.createAction("&Save intensities and spots...", self.saveIntensity,
QKeySequence.Save, None,
"Save the calculated intensities to a text file.")
self.fileSavePlotAction = self.createAction("&Save plot...", self.plotwid.save,
QKeySequence("Ctrl+a"), None,
"Save the plot to a pdf file.")
self.fileSaveScreenAction = self.createAction("&Save screenshot...", self.saveScreenShot,
QKeySequence("Ctrl+d"), None,
"Save image to a file.")
self.fileQuitAction = self.createAction("&Quit", self.fileQuit,
QKeySequence("Ctrl+q"), None,
"Close the application.")
self.fileSaveSpotsAction = self.createAction("&Save spot locations...", self.saveSpots,
QKeySequence("Ctrl+Shift+s"), None,
"Save the spots to a file.")
self.fileLoadSpotsAction = self.createAction("&Load spot locations...", self.loadSpots,
QKeySequence("Ctrl+l"), None,
"Load spots from a file.")
self.fileSaveCenterAction = self.createAction("&Save center location...", self.saveCenter,
QKeySequence("Ctrl+Shift+c"), None,
"Save the center to a file.")
self.fileLoadCenterAction = self.createAction("&Load center location...", self.loadCenter,
QKeySequence("Ctrl+Shift+l"), None,
"Load center from a file.")
# Disable actions that are not immediately available
for action in [self.fileSaveAction,
self.fileSavePlotAction,
self.fileSaveScreenAction,
self.fileSaveSpotsAction,
self.fileLoadSpotsAction,
self.fileSaveCenterAction,
self.fileLoadCenterAction]:
action.setEnabled(False)
self.fileActions = [self.fileOpenAction, None,
self.fileLoadSpotsAction, self.fileLoadCenterAction, None,
self.fileSaveAction, self.fileSavePlotAction, self.fileSaveScreenAction,
self.fileSaveSpotsAction, self.fileSaveCenterAction,
None, self.fileQuitAction]
# actions for "Help" menu
self.helpAction = self.createAction("&Help", self.helpBoxShow,
None, None,
"Show help")
self.aboutAction = self.createAction("&About", self.aboutwid.show,
None, None,
"About EasyLEED")
self.helpActions = [None, self.helpAction, None, self.aboutAction]
#### Create menu bar ####
fileMenu = self.menuBar().addMenu("&File")
self.addActions(fileMenu, self.fileActions)
processMenu = self.menuBar().addMenu("&Process")
self.addActions(processMenu, self.processActions)
self.enableProcessActions(False)
helpMenu = self.menuBar().addMenu("&Help")
self.addActions(helpMenu, self.helpActions)
#### Create tool bar ####
toolBar = self.addToolBar("&Toolbar")
# adding actions to the toolbar, addActions-function creates a separator with "None"
self.toolBarActions = [self.fileQuitAction, None, self.fileOpenAction, None,
self.processRunAction, None, self.processPauseAction, None, processStopAction, None,
processPlotOptions, None, processSetParameters, None,
self.processRemoveSpot, None, processRestartAction]
self.addActions(toolBar, self.toolBarActions)
#### Create status bar ####
self.statusBar().showMessage("Ready", 5000)
### Create buttons and custom energy button and text in statusbar
self.prevButton = QToolButton(self)
self.prevButton.setArrowType(Qt.LeftArrow)
self.prevButton.setEnabled(False)
self.prevButton.setToolTip("Previous image")
self.nextButton = QToolButton(self)
self.nextButton.setArrowType(Qt.RightArrow)
self.nextButton.setEnabled(False)
self.nextButton.setToolTip("Next image")
self.custEnergyButton = QPushButton("Set eV", self)
self.custEnergyButton.setCheckable(True)
self.custEnergyButton.setEnabled(False)
self.custEnergyButton.setToolTip("Push to set custom energy")
self.custEnergyText = QLineEdit()
self.custEnergyText.setToolTip("Press Enter to set")
### Create slider
self.slider = QSlider(Qt.Horizontal)
self.slider.setEnabled(False)
### Add buttons, slider and custom energy button and text in statusbar
self.statusBar().addPermanentWidget(self.prevButton)
self.statusBar().addPermanentWidget(self.nextButton)
self.statusBar().addPermanentWidget(self.slider)
self.statusBar().addPermanentWidget(self.custEnergyButton)
self.statusBar().addWidget(self.custEnergyText)
### Create event connector for slider and buttons in statusbar
self.slider.sliderMoved.connect(self.slider_moved)
self.prevButton.clicked.connect(self.prevBtnClicked)
self.nextButton.clicked.connect(self.nextBtnClicked)
self.custEnergyButton.clicked.connect(self.custEnBtnClicked)
self.custEnergyText.returnPressed.connect(self.setCustEnergy)
### Create event connector for enabling fast changes to smoothing parameters
self.parametersettingwid.smoothPoints.editingFinished.connect(self.liveSmoothParameters)
self.parametersettingwid.smoothSpline.editingFinished.connect(self.liveSmoothParameters)
[docs] def slider_moved(self, sliderNewPos):
"""
This function tracks what to do with a slider movement.
"""
diff = self.sliderCurrentPos - sliderNewPos
if diff > 0:
for i in range(0, diff):
self.previous()
else:
for i in range(diff, 0):
self.next_()
self.sliderCurrentPos = sliderNewPos
def prevBtnClicked(self):
self.worker = Worker(self.scene.spots, self.scene.center, self.current_energy, parent=self)
self.previous()
self.worker.process(self.loader.goto(self.current_energy))
self.sliderCurrentPos -= 1
self.slider.setValue(self.sliderCurrentPos)
def nextBtnClicked(self):
self.worker = Worker(self.scene.spots, self.scene.center, self.current_energy, parent=self)
self.next_()
self.worker.process(self.loader.goto(self.current_energy))
[docs] def custEnBtnClicked(self):
""" Action when custom energy button is clicked"""
if self.custEnergyButton.isChecked():
self.custEnergyText.show()
self.custEnergyText.setText("%s" % self.current_energy)
else:
self.setCustEnergy()
self.custEnergyText.hide()
[docs] def setCustEnergy(self):
""" Take energy from custom energy text and move the corresponding frame"""
self.worker = Worker(self.scene.spots, self.scene.center, self.current_energy, parent=self)
self.goto(float(self.custEnergyText.text()))
self.worker.process(self.loader.goto(self.current_energy))
[docs] def liveSmoothParameters(self):
""" Real time setting smoothing parameters from Parameter Settings panel into actual smoothed curve """
config.GraphicsScene_smoothPoints = self.parametersettingwid.smoothPoints.value()
config.GraphicsScene_smoothSpline = self.parametersettingwid.smoothSpline.value()
if self.plotwid.smoothCheck.isChecked():
self.plotwid.updatePlot()
[docs] def addActions(self, target, actions):
"""
Convenience function that adds the actions to the target.
If an action is None a separator will be added.
"""
for action in actions:
if action is None:
target.addSeparator()
else:
target.addAction(action)
[docs] def createAction(self, text, slot=None, shortcut=None, icon=None,
tip=None, checkable=False, signal="triggered()"):
""" Convenience function that creates an action with the specified attributes. """
action = QAction(text, self)
if icon is not None:
action.setIcon(QIcon(":/{0}.png".format(icon)))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
if slot is not None:
action.triggered.connect(slot)
if checkable:
action.setCheckable(True)
return action
def enableProcessActions(self, enable):
for action in self.processActions:
if action:
action.setEnabled(enable)
def next_(self):
try:
image = self.loader.next()
except StopIteration:
self.statusBar().showMessage("Reached last picture", 5000)
else:
self.setImage(image)
self.sliderCurrentPos += 1
self.slider.setValue(self.sliderCurrentPos)
def previous(self):
try:
image = self.loader.previous()
except StopIteration:
self.statusBar().showMessage("Reached first picture", 5000)
else:
self.setImage(image)
self.sliderCurrentPos -= 1
self.slider.setValue(self.sliderCurrentPos)
def goto(self, energy):
if energy >= 0:
try:
image = self.loader.goto(energy)
except:
self.statusBar().showMessage("Outside of energy range", 5000)
else:
self.setImage(image)
self.sliderCurrentPos = self.loader.index
self.slider.setValue(self.sliderCurrentPos)
else:
self.statusBar().showMessage("Energy must be positive", 5000)
[docs] def restart(self):
""" Delete stored plot information and start fresh """
self.scene.removeAll()
self.loader.restart()
self.setImage(self.loader.next())
self.sliderCurrentPos = 1
self.slider.setValue(1)
self.fileSaveSpotsAction.setEnabled(False)
def setImage(self, image):
npimage, energy = image
qimage = npimage2qimage(npimage)
self.view.setSceneRect(QRectF(qimage.rect()))
if config.GraphicsScene_intensTimeOn:
labeltext = "Frame: %s" % energy
else:
labeltext = "Energy: %s eV" % energy
self.scene.setBackground(qimage, labeltext)
self.current_energy = energy
def saveIntensity(self):
filename = 'intensities_spots.csv'
filename = qt_filedialog_convert(QFileDialog.getSaveFileName(self,
"Save intensities to a file",
filename))
if filename:
self.worker.saveIntensity(filename)
[docs] def fileOpen(self):
""" Prompts the user to select input image files."""
self.scene.removeAll()
files = qt_filedialog_convert(QFileDialog.getOpenFileNames(self,
"Open LEED images",
filter="Image files (%s)" % (" ".join(AllImageLoader.supported_extensions()))))
self.slider.setRange(1, len(files)+1)
try:
self.loader = AllImageLoader(files, config.IO_energyRegex)
self.setImage(self.loader.next())
except IOError as err:
self.statusBar().showMessage('IOError: ' + str(err), 5000)
except StopIteration:
self.statusBar().showMessage('No image file selected', 5000)
else:
self.enableProcessActions(True)
self.prevButton.setEnabled(True)
self.nextButton.setEnabled(True)
self.slider.setEnabled(True)
self.custEnergyButton.setEnabled(True)
self.fileSaveScreenAction.setEnabled(True)
self.fileLoadSpotsAction.setEnabled(True)
self.fileLoadCenterAction.setEnabled(True)
self.sliderCurrentPos = 1
self.slider.setValue(self.sliderCurrentPos)
def highlightSelSpot(self):
### Highlight the plot corresponding to a selected spot ###
if hasattr(self.plotwid,"lines_map"):
for _,lines in six.iteritems(self.plotwid.lines_map):
lines.set_linewidth(1)
try:
line = self.plotwid.lines_map[self.scene.selectedItems()[0]]
line.set_linewidth(3)
self.plotwid.updatePlot()
except:
pass
def removeLastSpot(self):
for item in self.scene.items():
if type(item) == QGraphicsSpotItem:
self.scene.removeItem(item)
try:
line = self.plotwid.lines_map[item]
line.remove()
self.plotwid.updatePlot()
except:
pass
break
try:
self.scene.spots.remove(self.scene.spots[-1])
except IndexError:
self.statusBar().showMessage('No spots to be removed', 5000)
if len(self.scene.items(0)) == 1:
self.fileSaveSpotsAction.setEnabled(False)
def stopProcessing(self):
self.stopped = True
def pauseProcessing(self):
if hasattr(self, "paused"):
if self.paused:
self.paused = False
else:
self.paused = True
def plot(self):
if hasattr(self,'worker'):
self.plotwid.setupPlot(self.worker)
# can save the plot now
self.fileSavePlotAction.setEnabled(True)
else:
self.statusBar().showMessage("Please run acquisition first.", 5000)
def run(self):
if len(self.scene.spots) == 0:
self.statusBar().showMessage("No integration window selected.", 5000)
else:
time_before = time.time()
self.initial_energy = self.current_energy
self.stopped = False
self.paused = False
self.processRunAction.setEnabled(False)
self.processPauseAction.setEnabled(True)
self.view.setInteractive(False)
self.slider.setEnabled(False)
self.processRemoveSpot.setEnabled(False)
self.scene.clearSelection()
self.worker = Worker(self.scene.spots, self.scene.center, self.current_energy, parent=self)
self.fileSaveAction.setEnabled(False)
self.fileSaveSpotsAction.setEnabled(True)
self.plotwid.clearPlotButton.setEnabled(False)
if config.GraphicsScene_livePlottingOn:
self.plot()
self.worker.process(self.loader.goto(self.current_energy))
for image in self.loader:
if self.stopped:
break
while self.paused:
self.processPauseAction.setText("Resume")
time.sleep(0.1)
self.view.setInteractive(True)
QApplication.processEvents()
self.worker.update_positions(self.scene.spots,self.scene.center, self.current_energy)
if self.stopped:
break
self.processPauseAction.setText("Pause")
#self.view.setInteractive(False)
QApplication.processEvents()
self.setImage(image)
self.worker.process(image)
QApplication.processEvents()
if config.GraphicsScene_livePlottingOn:
self.plotwid.updatePlot()
self.sliderCurrentPos += 1
self.slider.setValue(self.sliderCurrentPos)
self.view.setInteractive(True)
self.plotwid.clearPlotButton.setEnabled(True)
self.slider.setEnabled(True)
self.processRemoveSpot.setEnabled(True)
self.worker.createDataframe()
self.fileSaveAction.setEnabled(True)
self.processRunAction.setEnabled(True)
self.processPauseAction.setEnabled(False)
print("Total time acquisition:", time.time() - time_before, "s")
def disableInput(self):
for item in self.scene.spots:
item.setFlag(QGraphicsItem.ItemIsSelectable, False)
item.setFlag(QGraphicsItem.ItemIsFocusable, False)
item.setFlag(QGraphicsItem.ItemIsMovable, False)
def helpBoxShow(self):
webbrowser.open("http://andim.github.io/easyleed/userdoc.html")
[docs] def saveScreenShot(self):
""" Save Screenshot """
# savefile prompt
filename = "screenshot" + str(self.loader.energies[self.loader.index]) + "eV.png"
filename = qt_filedialog_convert(QFileDialog.getSaveFileName(self,
"Save the image to a file", filename,
filter="Image files (*.png *.bmp, *.jpg)"))
if filename:
pixMap = QWidget.grab(self.view) if get_qt_binding_name() == 'pyqt5' else QPixmap().grabWidget(self.view)
pixMap.save(filename)
[docs] def fileQuit(self):
"""Special quit-function as the normal window closing might leave something on the background """
self.pauseProcessing()
self.stopProcessing()
QApplication.closeAllWindows()
self.plotwid.canvas.close()
[docs] def saveSpots(self):
"""Saves the spot locations to a file, uses workers saveLoc-method"""
filename = "loc-spots_" + str(self.initial_energy) + "eV.csv"
filename = qt_filedialog_convert(QFileDialog.getSaveFileName(self,
"Save the spot locations to a file", filename))
if filename:
self.worker.saveLoc(filename)
[docs] def loadSpots(self):
"""Load Spots location from csv file"""
filename = qt_filedialog_convert(QFileDialog.getOpenFileName(self, 'Open spot location file'))
if filename:
try:
if os.path.splitext(filename)[1] == ".csv":
df = pd.read_csv(filename, skipinitialspace=True)
energy = df['Energy'].tolist()
numSpots = int((len(df.columns)-1)/3)
locationx = [df['x #'+str(s+1)].tolist() for s in range(numSpots)]
locationy = [df['y #'+str(s+1)].tolist() for s in range(numSpots)]
radius = [df['r #'+str(s+1)].tolist() for s in range(numSpots)]
# NEED TO FIGURE OUT HOW TO GET ALL THE SPOTS TO RESPECTIVE ENERGIES, now only loads the first energy's spots
# improving might involve modifying the algorithm for calculating intensity
else:
with open(filename, 'rb') as pkl_file:
location = pickle.load(pkl_file)
energy, locationx, locationy, radius = zip(*location)
numSpots = len(energy)
self.scene.removeAll()
for i in range(numSpots):
# only taking the first energy location, [0] -> [j] for all, but now puts every spot to the first energy
point = QPointF(locationx[i][0], locationy[i][0])
item = QGraphicsSpotItem(point, radius[i][0])
# adding the item to the gui
self.scene.addSpot(item)
except:
print("Invalid file for spot locations...")
[docs] def saveCenter(self):
"""Saves the center locations to a file, uses workers saveCenter-method"""
filename = 'loc-center.csv'
filename = qt_filedialog_convert(QFileDialog.getSaveFileName(self,
"Save the center location to a file",
filename))
if filename:
if hasattr(self, "worker"):
self.worker.saveCenter(filename)
else:
import csv
center = [["Energy","Center x","Center y"],
[self.current_energy, self.scene.center.x(), self.scene.center.y()]]
with open(filename, "w") as f:
writer = csv.writer(f, delimiter=',', lineterminator='\n')
writer.writerows(center)
[docs] def loadCenter(self):
"""Load Center location from csv file"""
filename = qt_filedialog_convert(QFileDialog.getOpenFileName(self, 'Open spot location file'))
if filename:
try:
if os.path.splitext(filename)[1] == ".csv":
df = pd.read_csv(filename, skipinitialspace=True)
cLocx = df['Center x'].tolist()
cLocy = df['Center y'].tolist()
else:
# pickle doesn't recognise the file opened by PyQt's openfile dialog as a file so 'normal' file processing
with open(filename, 'rb') as pkl_file:
location = pickle.load(pkl_file)
cLocx, cLocy = zip(*location)
self.scene.removeCenter()
point = QPointF(cLocx[0], cLocy[0])
item = QGraphicsCenterItem(point, config.QGraphicsCenterItem_size)
# adding the item to the gui
self.scene.clearSelection()
self.scene.addItem(item)
item.setSelected(True)
self.scene.center = item
self.scene.setFocusItem(item)
self.fileSaveCenterAction.setEnabled(True)
except:
print("Invalid file for center location...")
[docs]class Worker(QObject):
""" Worker that manages the spots.
spots_map:
- key: spot
- value: SpotModel, Tracker
"""
def __init__(self, spots, center, energy, parent=None):
super(Worker, self).__init__(parent)
self.spots_map = {}
self.tracker = {}
self.spots = spots
for i in range(len(self.spots)):
pos = self.spots[i].scenePos()
if center:
self.tracker[i] = Tracker(pos.x(), pos.y(), self.spots[i].radius(), energy, center.x(), center.y(),
input_precision=config.Tracking_inputPrecision,
window_scaling=config.Tracking_windowScalingOn)
else:
self.tracker[i] = Tracker(pos.x(), pos.y(), self.spots[i].radius(), energy,
input_precision=config.Tracking_inputPrecision,
window_scaling=config.Tracking_windowScalingOn)
self.spots_map[self.spots[i]] = (QSpotModel(self), self.tracker[i])
for view, tup in six.iteritems(self.spots_map):
# view = QGraphicsSpotItem, tup = (QSpotModel, tracker) -> tup[0] = QSpotModel
tup[0].positionChanged.connect(view.onPositionChange)
tup[0].radiusChanged.connect(view.onRadiusChange)
self.pdframe = pd.DataFrame()
def update_positions(self, spots, center, energy):
for i in range(len(spots)):
#for spot in self.scene.spots:
pos = spots[i].scenePos()
if center:
x_center = center.x()
y_center = center.y()
else:
x_center = None
y_center = None
self.tracker[i].init_tracker(pos.x(), pos.y(), spots[i].radius(), energy,
x_center, y_center,
input_precision=config.Tracking_inputPrecision,
window_scaling=config.Tracking_windowScalingOn)
def isPausedSetFlag(self, flag):
self.isPausedFlag = flag
def process(self, image):
if config.GraphicsScene_intensTimeOn:
print("Current frame: " + str(self.parent().current_energy))
else:
print("Current image energy: " + str(self.parent().current_energy) + "eV")
for model, tracker in six.itervalues(self.spots_map):
tracker_result = tracker.feed_image(image)
# feed_image returns x, y, intensity, energy and radius
model.update(*tracker_result)
[docs] def numProcessed(self):
""" Return the number of processed images. """
return len(next(six.itervalues(self.spots_map))[0].m.energy)
[docs] def createDataframe(self):
""" Create internal dataframe with intensities, spot locations, and center """
spots = self.parent().scene.spots
bs = config.Processing_backgroundSubstractionOn
intensities = [self.spots_map[spot][0].m.intensity for spot in spots]
energy = self.spots_map[spots[0]][0].m.energy
# Extract average spot intensity
intensity = np.zeros(self.numProcessed())
for model, tracker in six.itervalues(self.spots_map):
intensity += model.m.intensity
intensity = np.asarray([i/len(self.spots_map) for i in intensity])
# Save spot intensities in dataframe
#self.pdframe = pd.DataFrame({'Energy': energy})
self.pdframe['Energy'] = energy
for s in range(len(spots)):
self.pdframe['Intensity #'+str(s+1)] = self.spots_map[spots[s]][0].m.intensity
self.pdframe['Average'] = intensity
numEnergies = len(self.spots_map[spots[0]][0].m.x)
# Save spots coordinates and radius in dataframe
for s in range(len(spots)):
self.pdframe = self.pdframe.join(pd.DataFrame({\
'x #'+str(s+1) : [self.spots_map[spots[s]][0].m.x[i] for i in range(numEnergies)],
'y #'+str(s+1) : [self.spots_map[spots[s]][0].m.y[i] for i in range(numEnergies)],
'r #'+str(s+1) : [self.spots_map[spots[s]][0].m.radius[i] for i in range(numEnergies)]}))
# Save Center coordinates in dataframe
if hasattr(self.parent().scene.center, "x"):
self.pdframe['Center x'] = self.parent().scene.center.x()
self.pdframe['Center y'] = self.parent().scene.center.y()
else:
pass
self.pdframe['Background substraction'] = bs
[docs] def saveIntensity(self, filename):
"""save intensities"""
self.pdframe.to_csv(filename, sep=',', index=False)
[docs] def saveLoc(self, filename):
"""save spots location"""
spots = self.parent().scene.spots
locationCols = ['Energy']
for s in range(len(spots)):
locationCols.extend(['x #'+str(s+1), 'y #'+str(s+1),'r #'+str(s+1)])
self.pdframe[locationCols].to_csv(filename, sep=',', index=False)
[docs] def saveCenter(self, filename):
"""save center location"""
spots = self.parent().scene.spots
centerCols = ['Energy']
centerCols.extend(['Center x', 'Center y'])
self.pdframe[centerCols].to_csv(filename, sep=',', index=False)