#/*##########################################################################
# Copyright (C) 2004-2015 T. Rueter, European Synchrotron Radiation Facility
#
# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
# the ESRF by the Software group.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
#############################################################################*/
__author__ = "Tonn Rueter - ESRF Data Analysis"
__contact__ = "sole@esrf.fr"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
import sys
from os.path import isdir as osPathIsDir
from os.path import basename as osPathBasename
from os.path import join as osPathJoin
import numpy
from PyMca5.PyMcaMath.fitting.SpecfitFuns import upstep, downstep
from PyMca5.PyMca import PyMcaQt as qt
from PyMca5.PyMca import PlotWindow as DataDisplay
from PyMca5.PyMca import Elements
from PyMca5.PyMca import ConfigDict
from PyMca5.PyMca import PyMcaDataDir, PyMcaDirs
from PyMca5.PyMca import QSpecFileWidget
from PyMca5.PyMca import SpecFileDataSource
from PyMca5.PyMcaGui import IconDict
if hasattr(qt, "QString"):
QString = qt.QString
else:
QString = str
QStringList = list
if hasattr(qt, "QStringList"):
QStringList = qt.QStringList
else:
QStringList = list
DEBUG = 0
NEWLINE = '\n'
[docs]class Calculations(object):
def __init__(self):
pass
[docs] def cumtrapz(self, y, x=None, dx=1.0):
y = y[:]
if x is None:
x = numpy.arange(len(y), dtype=y.dtype) * dx
else:
x = x[:]
if not numpy.all(numpy.diff(x) > 0.):
# assure monotonically increasing x
idx = numpy.argsort(x)
x = numpy.take(x, idx)
y = numpy.take(y, idx)
# Avoid dublicates
x.ravel()
idx = numpy.nonzero(numpy.diff(x) > 0)[0]
x = numpy.take(x, idx)
y = numpy.take(y, idx)
return numpy.cumsum(.5 * numpy.diff(x) * (y[1:] + y[:-1]))
[docs] def magneticMoment(self, p, q, r, n, econf):
"""
Input
-----
:param p: Integral over the (first) edge of the XMCD (difference) signal
:type p: Float
:param q: Integral over the (second) edge of the XMCD (difference) signal
:type q: Float
:param r: Integral over the complete XAS signal
:type r: Float
:param n: Electron occupation number of the sample material
:type n: Float
:param econf: Determines if material is of 3d or 4f type and thus the number of electronic states in the outer shell
:type econf: String
Returns the orbital resp. the spin part of the magnetic moment
Paper references:
3d materials: Chen et al., Phys. Rev. Lett., 75(1), 152
4f materials: Krishnamurthy et al., Phys. Rev. B, 79(1), 014426
"""
mOrbt, mSpin, mRatio = None, None, None
# Check if r is non-zero
if r == 0.:
raise ZeroDivisionError()
# Determine number of states in outer shell
if econf == '3d':
if DEBUG >= 1:
print('Calculations.magneticMoment -- considering 3d material:')
print('\tp: %s, q: %s, r:%s'%(str(p),str(q),str(r)))
nMax = 10.
# Calculate Integrals
if q is not None:
#mOrbt = abs(-4./3. * q * (nMax - n) / (2.*r))
mOrbt = abs(-2./3. * q * (nMax - n) / r)
if (q is not None) and (p is not None):
#mSpin = abs((6.*p - 4.*q) * (nMax - n) / (2.*r))
mSpin = abs((3.*p - 2.*q) * (nMax - n) / r)
mRatio = abs(2.*q/(9.*p-6.*q))
elif econf == '4f':
if DEBUG >= 1:
print('Calculations.magneticMoment -- considering 4f material:')
print('\tp: %s, q: %s, r:%s'%(str(p),str(q),str(r)))
nMax = 14.
if q is not None:
mOrbt = abs(q * (nMax - n) / r)
if (q is not None) and (p is not None) and (r is not None):
mSpin = abs((3.*q - 5.*p) * (nMax - n) / (2. * r))
mRatio = mOrbt / mSpin
else:
raise ValueError('Calculations.magneticMoment -- Element must either be 3d or 4f type!')
return mOrbt, mSpin, mRatio
[docs]class MarkerSpinBox(qt.QDoubleSpinBox):
intersectionsChangedSignal = qt.pyqtSignal()
def __init__(self, window, plotWindow, label='', parent=None):
qt.QDoubleSpinBox.__init__(self, parent)
# Attributes
self.label = label
self.window = window
self.plotWindow = plotWindow
#self.graph = graph
self.markerID = self.plotWindow.insertXMarker(0.,
legend=label,
text=label)
# Initialize
self.setMinimum(0.)
self.setMaximum(10000.)
self.setValue(0.)
# Connects
self.plotWindow.sigPlotSignal.connect(self._handlePlotSignal)
self.valueChanged.connect(self._valueChanged)
def getIntersections(self):
dataList = self.plotWindow.getAllCurves()
resDict = {}
pos = self.value()
if not isinstance(pos, float):
return
for x, y, legend, info in dataList:
res = float('NaN')
if numpy.all(pos < x) or numpy.all(x < pos):
continue
#raise ValueError('Marker outside of data range')
if pos in x:
idx = numpy.where(x == pos)
res = y[idx]
else:
# Intepolation needed, assume well
# behaved data (c.f. copy routine)
lesserIdx = numpy.nonzero(x < pos)[0][-1]
greaterIdx = numpy.nonzero(x > pos)[0][0]
dy = y[lesserIdx] - y[greaterIdx]
dx = x[lesserIdx] - x[greaterIdx]
res = dy/dx * (pos - x[lesserIdx]) + y[lesserIdx]
resDict[legend] = (pos, res)
return resDict
def hideMarker(self):
self.plotWindow.removeMarker(self.label)
self.markerID = None
def showMarker(self):
self.plotWindow.removeMarker(self.label)
self.markerID = self.plotWindow.insertXMarker(
self.value(),
legend=self.label,
text=self.label,
color='blue',
selectable=False,
draggable=True)
def _setMarkerFollowMouse(self, windowTitle):
windowTitle = str(windowTitle)
if self.window == windowTitle:
# Blue, Marker is active
color = 'blue'
draggable = True
else:
# Black, marker is inactive
color = 'k'
draggable = False
# Make shure that the marker is deleted
# If marker is not present, removeMarker just passes..
self.markerID = self.plotWindow.insertXMarker(
self.value(),
legend=self.label,
text=self.label,
color=color,
selectable=False,
draggable=draggable)
def _handlePlotSignal(self, ddict):
if ddict['event'] != 'markerMoving':
return
if ddict['label'] != self.label:
return
markerPos = ddict['x']
self.blockSignals(True)
self.setValue(markerPos)
self.blockSignals(False)
self.intersectionsChangedSignal.emit()
def _valueChanged(self, val):
try:
val = float(val)
except ValueError:
if DEBUG == 1:
print('_valueChanged -- Sorry, it ain\'t gonna float: %s'%str(val))
return
# Marker of same label as self.label gets replaced..
self.markerID = self.plotWindow.insertXMarker(
val,
legend=self.label,
text=self.label,
color='blue',
selectable=False,
draggable=True)
self.intersectionsChangedSignal.emit()
[docs]class LineEditDisplay(qt.QLineEdit):
def __init__(self, controller, ddict=None, unit='', parent=None):
qt.QLineEdit.__init__(self, parent)
self.setReadOnly(True)
self.setAlignment(qt.Qt.AlignRight)
if ddict is None:
self.ddict = {}
else:
self.ddict = ddict
self.unit = unit
self.setMaximumWidth(120)
self.controller = controller
if isinstance(self.controller, qt.QComboBox):
self.controller.currentIndexChanged['QString'].connect(self.setText)
elif isinstance(self.controller, qt.QDoubleSpinBox):
# Update must be triggered otherwise
#self.controller.valueChanged['QString'].connect(self.setText)
pass
else:
raise ValueError('LineEditDisplay: Controller must be of type QComboBox or QDoubleSpinBox')
#self.controller.destroyed.connect(self.destroy)
[docs] def updateDict(self, ddict):
# Only relevant if type(controller) == QComboBox
self.ddict = ddict
[docs] def updateUnit(self, unit):
self.unit = unit
[docs] def checkController(self):
if isinstance(self.controller, qt.QComboBox):
tmp = self.controller.currentText()
elif isinstance(self.controller, qt.QDoubleSpinBox):
tmp = self.controller.value()
else:
if DEBUG == 1:
print('LineEditDisplay.checkController -- Reached untreated case, setting empty string')
tmp = ''
self.setText(tmp)
[docs] def setText(self, inp):
inp = str(inp)
if isinstance(self.controller, qt.QComboBox):
if inp == '':
text = ''
else:
tmp = self.ddict.get(inp,None)
if tmp is not None:
try:
text = '%.2f meV'%(1000. * float(tmp))
except ValueError:
text = 'NaN'
else:
text = '---'
elif isinstance(self.controller, qt.QDoubleSpinBox):
text = inp + ' ' + self.unit
else:
if DEBUG == 1:
print('LineEditDisplay.setText -- Reached untreated case, setting empty string')
text = ''
qt.QLineEdit.setText(self, text)
[docs]class SumRulesWindow(qt.QMainWindow):
# Curve labeling
__xasBGmodel = 'xas BG model'
# Tab names
__tabElem = 'element'
__tabBG = 'background'
__tabInt = 'integration'
# Marker names
__preMin = 'Pre Min'
__preMax = 'Pre Max'
__postMin = 'Post Min'
__postMax = 'Post Max'
__intP = 'p'
__intQ = 'q'
__intR = 'r'
# Lists
tabList = [__tabElem,
__tabBG,
__tabInt]
xasMarkerList = [__preMin,
__preMax,
__postMin,
__postMax]
xmcdMarkerList = [__intP,
__intQ,
__intR]
edgeMarkerList = []
# Elements with 3d final state
transitionMetals = ['Sc', 'Ti', 'V', 'Cr', 'Mn',
'Fe', 'Co', 'Ni', 'Cu']
# Elements with 4f final state
rareEarths = ['La', 'Ce', 'Pr', 'Nd', 'Pm',
'Sm', 'Eu', 'Gd', 'Tb', 'Dy',
'Ho', 'Er', 'Tm', 'Yb']
elementsDict = {
'' : [],
'3d': transitionMetals,
'4f': rareEarths
}
# Electron final states
electronConfs = ['3d','4f']
# Occuring Transitions
occuringTransitions = ['L3M4', 'L3M5', 'L2M4', 'M5O3','M4O3']
# Signals
tabChangedSignal = qt.pyqtSignal('QString')
def __init__(self, parent=None):
qt.QMainWindow.__init__(self, parent)
self.setWindowTitle('Sum Rules Tool')
if hasattr(DataDisplay,'PlotWindow'):
self.plotWindow = DataDisplay.PlotWindow(
parent=self,
backend=None,
plugins=False, # Hide plugin tool button
newplot=False, # Hide mirror active curve, ... functionality
roi=False, # No ROI widget
control=False, # Hide option button
position=True, # Show x,y position display
kw={'logx': False, # Hide logarithmic x-scale tool button
'logy': False, # Hide logarithmic y-scale tool button
'flip': False, # Hide whatever this does
'fit': False}) # Hide simple fit tool button
self.plotWindow._buildLegendWidget()
else:
self.plotWindow = DataDisplay.ScanWindow(self)
# Hide Buttons in the toolbar
if hasattr(self.plotWindow,'scanWindowInfoWidget'):
# Get rid of scanInfoWidget
self.plotWindow.scanWindowInfoWidget.hide()
self.plotWindow.graph.enablemarkermode()
# Hide unnecessary buttons in the toolbar
toolbarChildren = self.plotWindow.toolBar
# QWidget.findChildren(<qt-type>) matches
# all child widgets with the specified type
toolbarButtons = toolbarChildren.findChildren(qt.QToolButton)
toolbarButtons[3].hide() # LogX
toolbarButtons[4].hide() # LogY
toolbarButtons[6].hide() # Simple Fit
toolbarButtons[7].hide() # Average Plotted Curves
toolbarButtons[8].hide() # Derivative
toolbarButtons[9].hide() # Smooth
toolbarButtons[11].hide() # Set active to zero
toolbarButtons[12].hide() # Subtract active curve
toolbarButtons[13].hide() # Save active curve
toolbarButtons[14].hide() # Plugins
else:
self.plotWindow
self.__savedConf = False
self.__savedData = False
# Marker Handling
# spinboxDict connects marker movement to spinbox
# keys() -> id(MarkerSpinBox)
# values() -> MarkerSpinBox
self.spinboxDict = {}
self.valuesDict = dict(
[(item, {}) for item in self.tabList])
# Tab Widget
tabIdx = 0
self.tabWidget = qt.QTabWidget()
for window in self.tabList:
if window == self.__tabElem:
# BEGIN sampleGB
sampleGB = qt.QGroupBox('Sample definition')
sampleLayout = qt.QVBoxLayout()
sampleGB.setLayout(sampleLayout)
# electron shell combo box
self.elementEConfCB = qt.QComboBox()
self.elementEConfCB.setMinimumWidth(100)
self.elementEConfCB.addItems(['']+self.electronConfs)
self.elementEConfCB.currentIndexChanged['QString'].connect(self.setElectronConf)
elementEConfLayout = qt.QHBoxLayout()
elementEConfLayout.setContentsMargins(0,0,0,0)
elementEConfLayout.addWidget(qt.QLabel('Electron configuration'))
elementEConfLayout.addWidget(qt.HorizontalSpacer())
elementEConfLayout.addWidget(self.elementEConfCB)
elementEConfWidget = qt.QWidget()
elementEConfWidget.setLayout(elementEConfLayout)
sampleLayout.addWidget(elementEConfWidget)
# Element selection combo box
self.elementCB = qt.QComboBox()
self.elementCB.setMinimumWidth(100)
self.elementCB.addItems([''])
self.elementCB.currentIndexChanged['QString'].connect(self.getElementInfo)
elementLayout = qt.QHBoxLayout()
elementLayout.setContentsMargins(0,0,0,0)
elementLayout.addWidget(qt.QLabel('Element'))
elementLayout.addWidget(qt.HorizontalSpacer())
elementLayout.addWidget(self.elementCB)
elementWidget = qt.QWidget()
elementWidget.setLayout(elementLayout)
sampleLayout.addWidget(elementWidget)
# electron occupation number
self.electronOccupation = qt.QLineEdit('e.g. 3.14')
self.electronOccupation.setMaximumWidth(120)
electronOccupationValidator = qt.QDoubleValidator(self.electronOccupation)
electronOccupationValidator.setBottom(0.)
electronOccupationValidator.setTop(14.)
self.electronOccupation.setValidator(electronOccupationValidator)
electronOccupationLayout = qt.QHBoxLayout()
electronOccupationLayout.setContentsMargins(0,0,0,0)
electronOccupationLayout.addWidget(qt.QLabel('Electron Occupation Number'))
electronOccupationLayout.addWidget(qt.HorizontalSpacer())
electronOccupationLayout.addWidget(self.electronOccupation)
electronOccupationWidget = qt.QWidget()
electronOccupationWidget.setLayout(electronOccupationLayout)
sampleLayout.addWidget(electronOccupationWidget)
# END sampleGB
# BEGIN absorptionGB: X-ray absorption edge
# selection combo box by transition (L3M1, etc.)
absorptionGB = qt.QGroupBox('X-ray absorption edges')
absorptionLayout = qt.QVBoxLayout()
absorptionGB.setLayout(absorptionLayout)
self.edge1CB = qt.QComboBox()
self.edge1CB.setMinimumWidth(100)
self.edge1CB.addItems([''])
self.edge1Line = LineEditDisplay(self.edge1CB)
edge1Layout = qt.QHBoxLayout()
edge1Layout.setContentsMargins(0,0,0,0)
edge1Layout.addWidget(qt.QLabel('Edge 1'))
edge1Layout.addWidget(qt.HorizontalSpacer())
edge1Layout.addWidget(self.edge1CB)
edge1Layout.addWidget(self.edge1Line)
edge1Widget = qt.QWidget()
edge1Widget.setLayout(edge1Layout)
absorptionLayout.addWidget(edge1Widget)
self.edge2CB = qt.QComboBox()
self.edge2CB.setMinimumWidth(100)
self.edge2CB.addItems([''])
self.edge2Line = LineEditDisplay(self.edge2CB)
edge2Layout = qt.QHBoxLayout()
edge2Layout.setContentsMargins(0,0,0,0)
edge2Layout.addWidget(qt.QLabel('Edge 2'))
edge2Layout.addWidget(qt.HorizontalSpacer())
edge2Layout.addWidget(self.edge2CB)
edge2Layout.addWidget(self.edge2Line)
edge2Widget = qt.QWidget()
edge2Widget.setLayout(edge2Layout)
absorptionLayout.addWidget(edge2Widget)
absorptionLayout.setAlignment(qt.Qt.AlignTop)
# END absorptionGB
# Combine sampleGB & absorptionGB in one Line
topLineLayout = qt.QHBoxLayout()
topLineLayout.setContentsMargins(0,0,0,0)
topLineLayout.addWidget(sampleGB)
topLineLayout.addWidget(absorptionGB)
topLine = qt.QWidget()
topLine.setLayout(topLineLayout)
# BEGIN tab layouting
elementTabLayout = qt.QVBoxLayout()
elementTabLayout.setContentsMargins(1,1,1,1)
elementTabLayout.addWidget(topLine)
elementTabLayout.addWidget(qt.VerticalSpacer())
elementTabWidget = qt.QWidget()
elementTabWidget.setLayout(elementTabLayout)
self.tabWidget.addTab(
elementTabWidget,
window.upper())
self.tabWidget.setTabToolTip(tabIdx,
'Shortcut: F2\n'
+'Define sample element here')
# END tab layouting
self.valuesDict[self.__tabElem]\
['element'] = self.elementCB
self.valuesDict[self.__tabElem]\
['electron configuration'] = self.elementEConfCB
self.valuesDict[self.__tabElem]\
['electron occupation'] = self.electronOccupation
self.valuesDict[self.__tabElem]\
['edge1Transition'] = self.edge1CB
self.valuesDict[self.__tabElem]\
['edge2Transition'] = self.edge2CB
self.valuesDict[self.__tabElem]\
['edge1Energy'] = self.edge1Line
self.valuesDict[self.__tabElem]\
['edge2Energy'] = self.edge2Line
self.valuesDict[self.__tabElem]['info'] = {}
elif window == self.__tabBG:
# BEGIN Pre/Post edge group box
prePostLayout = qt.QGridLayout()
prePostLayout.setContentsMargins(1,1,1,1)
for idx, markerLabel in enumerate(self.xasMarkerList):
# Estimate intial xpos by estimateInt
markerWidget, spinbox = self.addMarker(window=window,
label=markerLabel,
xpos=0.,
unit='[eV]')
self.valuesDict[self.__tabBG][markerLabel] = spinbox
if idx == 0:
posx, posy = 0,0
markerWidget.setContentsMargins(0,0,0,-8)
elif idx == 1:
posx, posy = 1,0
markerWidget.setContentsMargins(0,-8,0,0)
elif idx == 2:
posx, posy = 0,1
markerWidget.setContentsMargins(0,0,0,-8)
elif idx == 3:
posx, posy = 1,1
markerWidget.setContentsMargins(0,-8,0,0)
else:
raise IndexError('Index out of bounds: %d -> %s'\
%(idx, markerLabel))
prePostLayout.addWidget(markerWidget, posx, posy)
prePostGB = qt.QGroupBox('Pre/Post edge')
prePostGB.setLayout(prePostLayout)
# END Pre/Post edge group box
# BEGIN Edge group box
numberOfEdges = 2
edgeLayout = qt.QVBoxLayout()
edgeLayout.setContentsMargins(1,1,1,1)
for idx in range(numberOfEdges):
markerLabel = 'Edge %d'%(idx+1)
self.edgeMarkerList += [markerLabel]
markerWidget, spinbox = self.addMarker(window=window,
label=markerLabel,
xpos=0.,
unit='[eV]')
self.valuesDict[self.__tabBG][markerLabel] = spinbox
if idx == 0:
markerWidget.setContentsMargins(0,0,0,-8)
elif idx == (numberOfEdges-1):
markerWidget.setContentsMargins(0,-8,0,0)
else:
markerWidget.setContentsMargins(0,-8,0,-8)
edgeLayout.addWidget(markerWidget)
markerWidget.setEnabled(False)
edgeGB = qt.QGroupBox('Edge positions')
edgeGB.setLayout(edgeLayout)
# END Edge group box
# BEGIN Background model group box
stepRatio = qt.QDoubleSpinBox()
stepRatio.setMaximumWidth(100)
stepRatio.setAlignment(qt.Qt.AlignRight)
stepRatio.setMinimum(0.)
stepRatio.setMaximum(1.)
stepRatio.setSingleStep(.025)
stepRatio.setValue(.5)
stepRatioLayout = qt.QHBoxLayout()
stepRatioLayout.addWidget(qt.QLabel('Step ratio'))
stepRatioLayout.addWidget(qt.HorizontalSpacer())
stepRatioLayout.addWidget(stepRatio)
stepRatioWidget = qt.QWidget()
stepRatioWidget.setContentsMargins(0,0,0,-8)
stepRatioWidget.setLayout(stepRatioLayout)
stepWidth = qt.QDoubleSpinBox()
stepWidth.setMaximumWidth(100)
stepWidth.setAlignment(qt.Qt.AlignRight)
stepWidth.setMinimum(0.)
stepWidth.setMaximum(1000.)
stepWidth.setSingleStep(0.05)
stepWidth.setValue(.0) # Start with step function
stepWidthLayout = qt.QHBoxLayout()
stepWidthLayout.addWidget(qt.QLabel('Step width [eV]'))
stepWidthLayout.addWidget(qt.HorizontalSpacer())
stepWidthLayout.addWidget(stepWidth)
stepWidthWidget = qt.QWidget()
stepWidthWidget.setContentsMargins(0,-8,0,0)
stepWidthWidget.setLayout(stepWidthLayout)
fitControlLayout = qt.QVBoxLayout()
fitControlLayout.addWidget(stepRatioWidget)
fitControlLayout.addWidget(stepWidthWidget)
fitControlGB = qt.QGroupBox('Background model control')
fitControlGB.setLayout(fitControlLayout)
# END Background model group box
# Combine edge position and background model in single line
sndLine = qt.QWidget()
sndLineLayout = qt.QHBoxLayout()
sndLineLayout.setContentsMargins(1,1,1,1)
sndLine.setLayout(sndLineLayout)
sndLineLayout.addWidget(edgeGB)
sndLineLayout.addWidget(fitControlGB)
# Insert into tab
backgroundTabLayout = qt.QVBoxLayout()
backgroundTabLayout.setContentsMargins(1,1,1,1)
backgroundTabLayout.addWidget(prePostGB)
backgroundTabLayout.addWidget(sndLine)
backgroundTabLayout.addWidget(qt.VerticalSpacer())
backgroundWidget = qt.QWidget()
backgroundWidget.setLayout(backgroundTabLayout)
self.tabWidget.addTab(
backgroundWidget,
window.upper())
self.tabWidget.setTabToolTip(tabIdx,
'Shortcut: F3\n'
+'Model background here.')
stepRatio.valueChanged['double'].connect(self.estimateBG)
stepWidth.valueChanged['double'].connect(self.estimateBG)
self.valuesDict[self.__tabBG]\
['Step Ratio'] = stepRatio
self.valuesDict[self.__tabBG]\
['Step Width'] = stepWidth
elif window == self.__tabInt:
# BEGIN Integral marker groupbox
pqLayout = qt.QVBoxLayout()
pqLayout.setContentsMargins(0,-8,0,-8)
for markerLabel in self.xmcdMarkerList:
markerWidget, spinbox = self.addMarker(window=window,
label=markerLabel,
xpos=0.,
unit='[eV]')
self.valuesDict[self.__tabInt][markerLabel] = spinbox
if markerLabel == self.xmcdMarkerList[0]:
markerWidget.setContentsMargins(0,0,0,-8)
elif markerLabel == self.xmcdMarkerList[-1]:
# Last widget gets more content margin
# at the bottom
markerWidget.setContentsMargins(0,-8,0,0)
else:
markerWidget.setContentsMargins(0,-8,0,-8)
integralVal = qt.QLineEdit()
integralVal.setReadOnly(True)
integralVal.setMaximumWidth(120)
valLabel = qt.QLabel('Integral Value:')
mwLayout = markerWidget.layout()
mwLayout.addWidget(valLabel)
mwLayout.addWidget(integralVal)
pqLayout.addWidget(markerWidget)
#spinbox.valueChanged.connect(self.calcMagneticMoments)
spinbox.intersectionsChangedSignal.connect(self.calcMagneticMoments)
key = 'Integral ' + markerLabel
self.valuesDict[self.__tabInt][key] = integralVal
pqGB = qt.QGroupBox('XAS/XMCD integrals')
pqGB.setLayout(pqLayout)
# END Integral marker groupbox
# BEGIN magnetic moments groupbox
mmLayout = qt.QVBoxLayout()
mmLayout.setContentsMargins(0,-8,0,-8)
text = 'Spin Magnetic Moment'
mmLineLayout = qt.QHBoxLayout()
self.mmSpin = qt.QLineEdit()
self.mmSpin.setReadOnly(True)
self.mmSpin.setMaximumWidth(120)
mmLineLayout.addWidget(qt.QLabel(text))
mmLineLayout.addWidget(qt.HorizontalSpacer())
mmLineLayout.addWidget(qt.QLabel('mS = '))
mmLineLayout.addWidget(self.mmSpin)
mmLineWidget = qt.QWidget()
mmLineWidget.setLayout(mmLineLayout)
mmLineWidget.setContentsMargins(0,0,0,-8)
mmLayout.addWidget(mmLineWidget)
text = 'Orbital Magnetic Moment'
mmLineLayout = qt.QHBoxLayout()
self.mmOrbt = qt.QLineEdit()
self.mmOrbt.setReadOnly(True)
self.mmOrbt.setMaximumWidth(120)
mmLineLayout.addWidget(qt.QLabel(text))
mmLineLayout.addWidget(qt.HorizontalSpacer())
mmLineLayout.addWidget(qt.QLabel('mO = '))
mmLineLayout.addWidget(self.mmOrbt)
mmLineWidget = qt.QWidget()
mmLineWidget.setLayout(mmLineLayout)
mmLineWidget.setContentsMargins(0,-8,0,-8)
mmLayout.addWidget(mmLineWidget)
text = 'Ratio Magnetic Moments'
mmLineLayout = qt.QHBoxLayout()
self.mmRatio = qt.QLineEdit()
self.mmRatio.setReadOnly(True)
self.mmRatio.setMaximumWidth(120)
mmLineLayout.addWidget(qt.QLabel(text))
mmLineLayout.addWidget(qt.HorizontalSpacer())
mmLineLayout.addWidget(qt.QLabel('mO/mS = '))
mmLineLayout.addWidget(self.mmRatio)
mmLineWidget = qt.QWidget()
mmLineWidget.setLayout(mmLineLayout)
mmLineWidget.setContentsMargins(0,-8,0,0)
mmLayout.addWidget(mmLineWidget)
mmGB = qt.QGroupBox('Magnetic moments')
mmGB.setLayout(mmLayout)
# END magnetic moments groupbox
# Combine Integral marker groupbox and
# magnetic moments groupbox in single line
topLineLayout = qt.QHBoxLayout()
topLineLayout.setContentsMargins(0,0,0,0)
topLineLayout.addWidget(pqGB)
topLineLayout.addWidget(mmGB)
topLine = qt.QWidget()
topLine.setLayout(topLineLayout)
# BEGIN XMCD correction
self.xmcdDetrend = qt.QCheckBox()
self.xmcdDetrend.stateChanged['int'].connect(self.triggerDetrend)
xmcdDetrendLayout = qt.QHBoxLayout()
#xmcdDetrendLayout.setContentsMargins(0,0,0,1)
xmcdDetrendLayout.addWidget(qt.QLabel(
'Detrend XMCD Signal (Subtracts linear fit of pre-edge Region from the signal)'))
xmcdDetrendLayout.addWidget(qt.HorizontalSpacer())
xmcdDetrendLayout.addWidget(self.xmcdDetrend)
xmcdDetrendWidget = qt.QWidget()
xmcdDetrendWidget.setLayout(xmcdDetrendLayout)
xmcdDetrendGB = qt.QGroupBox('XMCD Data Preprocessing')
xmcdDetrendGB.setLayout(xmcdDetrendLayout)
xmcdDetrendLayout.addWidget(xmcdDetrendWidget)
# END XMCD correction
xmcdTabLayout = qt.QVBoxLayout()
xmcdTabLayout.setContentsMargins(2,2,2,2)
xmcdTabLayout.addWidget(topLine)
xmcdTabLayout.addWidget(xmcdDetrendGB)
xmcdTabLayout.addWidget(qt.VerticalSpacer())
xmcdWidget = qt.QWidget()
xmcdWidget.setLayout(xmcdTabLayout)
self.tabWidget.addTab(
xmcdWidget,
window.upper())
self.tabWidget.setTabToolTip(tabIdx,
'Shortcut: F4\n'
+'Assign Markers p, q and r here')
self.valuesDict[self.__tabInt]\
['Orbital Magnetic Moment'] = self.mmOrbt
self.valuesDict[self.__tabInt]\
['Spin Magnetic Moment'] = self.mmSpin
self.valuesDict[self.__tabInt]\
['Ratio Magnetic Moments'] = self.mmRatio
self.valuesDict[self.__tabInt]\
['XMCD Detrend'] = self.xmcdDetrend
tabIdx += 1
# END TabWidget
# Add to self.valuesDict
self.tabWidget.currentChanged['int'].connect(
self._handleTabChangedSignal)
# Estimate button in bottom of plot window layout
self.buttonEstimate = qt.QPushButton('Estimate', self)
self.buttonEstimate.setToolTip(
'Shortcut: CRTL+E\n'
+'Depending on the tab, estimate either the pre/post\n'
+'edge regions and edge positions or the positions of\n'
+'the p, q and r markers.')
self.buttonEstimate.setShortcut(qt.Qt.CTRL+qt.Qt.Key_E)
self.buttonEstimate.clicked.connect(self.estimate)
self.buttonEstimate.setEnabled(False)
self.plotWindow.toolBar.addSeparator()
self.plotWindow.toolBar.addWidget(self.buttonEstimate)
self.plotWindow.sigPlotSignal.connect(self._handlePlotSignal)
# Layout
mainWidget = qt.QWidget()
mainLayout = qt.QVBoxLayout()
mainLayout.addWidget(self.plotWindow)
mainLayout.addWidget(self.tabWidget)
mainLayout.setContentsMargins(1,1,1,1)
mainWidget.setLayout(mainLayout)
self.setCentralWidget(mainWidget)
#
# Data handling:
#
# Each is Tuple (x,y)
# type(x),type(y) == ndarray
self.xmcdData = None # XMCD Spectrum
self.xasData = None # XAS Spectrum
self.xasDataCorr = None # XAS minus Background model
self.xasDataBG = None # XAS Backgrouns
self.xmcdCorrData = None
# Integrated spectra: Notice that the shape
# diminished by one..
self.xmcdInt = None
self.xasInt = None
#
# File (name) handling
#
self.dataInputFilename = None
self.confFilename = None
self.baseFilename = None
self._createMenuBar()
def _createMenuBar(self):
# Creates empty menu bar, if none existed before
menu = self.menuBar()
menu.clear()
#
# 'File' Menu
#
ffile = menu.addMenu('&File')
openAction = qt.QAction('&Open Spec File', self)
openAction.setShortcut(qt.Qt.CTRL+qt.Qt.Key_O)
openAction.setStatusTip('Opened file')
openAction.setToolTip('Opens a data file (*.spec)')
openAction.triggered.connect(self.loadData)
loadAction = qt.QAction('&Load Configuration', self)
loadAction.setShortcut(qt.Qt.CTRL+qt.Qt.Key_L)
loadAction.setStatusTip('Loaded analysis file')
loadAction.setToolTip('Loads an existing analysis file (*.sra)')
loadAction.triggered.connect(self.loadConfiguration)
saveConfAction = qt.QAction('&Save Configuration', self)
saveConfAction.setShortcut(qt.Qt.CTRL+qt.Qt.Key_S)
saveConfAction.setStatusTip('Saved analysis file')
saveConfAction.setToolTip('Save analysis in file (*.sra)')
saveConfAction.triggered.connect(self.saveConfiguration)
saveConfAsAction = qt.QAction('Save &Configuration as', self)
saveConfAsAction.setShortcut(qt.Qt.SHIFT+qt.Qt.CTRL+qt.Qt.Key_S)
saveConfAsAction.setStatusTip('Saved analysis file')
saveConfAsAction.setToolTip('Save analysis in file (*.sra)')
saveConfAsAction.triggered.connect(self.saveConfigurationAs)
saveDataAction = qt.QAction('Save &Data', self)
saveDataAction.setShortcut(qt.Qt.CTRL+qt.Qt.Key_D)
saveDataAction.setStatusTip('Saved analysis file')
saveDataAction.setToolTip('Save analysis in file (*.sra)')
saveDataAction.triggered.connect(self.saveData)
saveDataAsAction = qt.QAction('Save D&ata as', self)
saveDataAsAction.setShortcut(qt.Qt.SHIFT+qt.Qt.CTRL+qt.Qt.Key_D)
saveDataAsAction.setStatusTip('Saved analysis file')
saveDataAsAction.setToolTip('Save analysis in file (*.sra)')
saveDataAsAction.triggered.connect(self.saveDataAs)
# Populate the 'File' menu
for action in [openAction,
loadAction,
'sep',
saveConfAction,
saveConfAsAction,
'sep',
saveDataAction,
saveDataAsAction,
'sep']:
if isinstance(action, qt.QAction):
ffile.addAction(action)
else:
ffile.addSeparator()
ffile.addAction('E&xit', self.close)
#
# 'Help' Menu
#
hhelp = menu.addMenu('&Help')
showHelpFileAction = qt.QAction('Show &documentation', self)
showHelpFileAction.setShortcut(qt.Qt.Key_F1)
showHelpFileAction.setStatusTip('')
showHelpFileAction.setToolTip('Opens the documentation (html-file) in the systems native web browser')
showHelpFileAction.triggered.connect(self.showInfoWindow)
# Populate the 'Help' menu
hhelp.addAction(showHelpFileAction)
def showInfoWindow(self):
"""
Opens a web browser and displays the help file
"""
helpFileName = qt.QDir(osPathJoin(PyMcaDataDir.PYMCA_DOC_DIR,
"HTML",
"SumRulesToolInfotext.html"))
qt.QDesktopServices.openUrl(qt.QUrl(helpFileName.absolutePath()))
def triggerDetrend(self, state):
if (state == qt.Qt.Unchecked) or\
(state == qt.Qt.PartiallyChecked):
# Replot original data
self.xmcdCorrData = None
else:
ddict = self.getValuesDict()
if self.xmcdData is None:
return
x, y = self.xmcdData
preMin = ddict[self.__tabBG][self.__preMin]
preMax = ddict[self.__tabBG][self.__preMax]
mask = numpy.nonzero((preMin <= x) & (x <= preMax))[0]
xFit = x.take(mask)
yFit = y.take(mask)
if (len(xFit) == 0) or (len(yFit) == 0):
return
# Fit linear model y = a*x + b
a, b = numpy.polyfit(xFit, yFit, 1)
trend = a*x + b
self.xmcdCorrData = (x, y-trend)
if self.getCurrentTab() == self.__tabInt:
self.plotOnDemand(self.__tabInt)
self.calcMagneticMoments()
def calcMagneticMoments(self):
ddict = self.valuesDict
pqr = []
mathObj = Calculations()
for marker in self.xmcdMarkerList:
if marker in [self.__intP, self.__intQ]:
if self.xmcdCorrData is not None:
curve = 'xmcd corr Int'
else:
curve = 'xmcd Int'
else:
curve = 'xas Int'
spinbox = ddict[self.__tabInt][marker]
integralVals = spinbox.getIntersections()
x, y = integralVals.get(curve, (float('NaN'),float('NaN')))
key = 'Integral ' + marker
lineEdit = ddict[self.__tabInt][key]
lineEdit.setText(str(y))
pqr += [y]
p, q, r = pqr
electronOccupation = ddict[self.__tabElem]['electron occupation']
try:
n = float(electronOccupation.text())
except ValueError:
if DEBUG == 1:
print('calcMM -- Could not convert electron occupation')
return
electronConfiguration = ddict[self.__tabElem]['electron configuration']
econf = str(electronConfiguration.currentText())
try:
mmO, mmS, mmR = mathObj.magneticMoment(p,q,r,n,econf)
except ValueError as e:
if DEBUG == 1:
print(e)
mmO, mmS, mmR = 3*['---']
self.mmOrbt.setText(str(mmO))
self.mmSpin.setText(str(mmS))
self.mmRatio.setText(str(mmR))
def loadData(self):
dial = LoadDichorismDataDialog()
dial.setDirectory(PyMcaDirs.outputDir)
if dial.exec_():
dataDict = dial.dataDict
else:
return
# Reset calculated data
self.xasDataCorr = None
self.xasDataBG = None
self.xmcdCorrData = None
self.xmcdInt = None
self.xasInt = None
x = dataDict['x']
xas = dataDict['xas']
xmcd = dataDict['xmcd']
self.dataInputFilename = dataDict['fn']
self.setRawData(x, xas, 'xas')
self.setRawData(x, xmcd, 'xmcd')
def saveDataAs(self):
self.baseFilename = None
self.__savedData = False
self.saveData()
def saveData(self):
# Saves spectral data that is calculated during
# the evaluation process:
# First scan: XAS BG-Modell XAS-BG
# Second scan: XAS/XMCD integrals
dataList = [self.xasData,
self.xasDataCorr,
self.xasDataBG,
self.xmcdInt,
self.xasInt]
if None in dataList:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Analysis Error')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('Analysis incomplete!\nCannot save generated data')
msg.exec_()
return False
if self.__savedData and self.baseFilename:
pass
else:
ddict = self.getValuesDict()
saveDir = PyMcaDirs.outputDir
filters = 'spec File (*.spec);;All files (*.*)'
baseFilename = qt.QFileDialog.getSaveFileName(self,
'Save Sum Rule Analysis Data',
saveDir,
filters)
if len(baseFilename) == 0:
# Leave self.baseFilename as it is..
#self.baseFilename = None
return False
else:
self.baseFilename = str(baseFilename)
if not self.baseFilename.endswith('.spec'):
# Append extension later
self.baseFilename += '.spec'
# Create filenames
specFilename = self.baseFilename
baseName = osPathBasename(self.dataInputFilename)
self.__savedData = False
# Acquire filehandle
try:
specFilehandle = open(specFilename, 'wb')
except IOError:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Analysis Error')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('Unable to open file \'%s\''%specFilename)
msg.exec_()
return False
delim = ' '
# 1. Background Modell, XAS, XAS-Background
# All share the same x-range
xSpec, yXas = self.xasData
xSpec, yBG = self.xasDataBG
xSpec, yXasBG = self.xasDataCorr
if self.xmcdCorrData:
xInt, yXmcd = self.xmcdCorrData
else:
xInt, yXmcd = self.xmcdData
dataSpec = numpy.vstack((xSpec, yXas, yBG, yXasBG, yXmcd)).T
# 2. Integrals
# Also share the same x-range
xInt, yXasInt = self.xasInt
xInt, yXmcdInt = self.xmcdInt
dataInt = numpy.vstack((xInt, yXasInt, yXmcdInt)).T
# Construct spectra output
outSpec = ''
outSpec += (NEWLINE + '#S 1 XAS data %s'%baseName + NEWLINE)
outSpec += ('#N %d'%5 + NEWLINE)
if self.xmcdCorrData:
outSpec += ('#L x XAS Background model XAS corrected XMCD corrected' + NEWLINE)
else:
outSpec += ('#L x XAS Background model XAS corrected XMCD' + NEWLINE)
for line in dataSpec:
tmp = delim.join(['%f'%num for num in line])
outSpec += (tmp + NEWLINE)
outSpec += (NEWLINE)
# Construct integral output
outInt = ''
outInt += ('#S 2 Integral data %s'%baseName + NEWLINE)
outInt += ('#N %d'%3 + NEWLINE)
if self.xmcdCorrData:
outInt += ('#L x XAS Int XMCD Int' + NEWLINE)
else:
outInt += ('#L x XAS Int XMCD Int corrected' + NEWLINE)
for line in dataInt:
tmp = delim.join(['%f'%num for num in line])
outInt += (tmp + NEWLINE)
outInt += (NEWLINE)
for output in [outSpec, outInt]:
specFilehandle.write(output.encode('ascii'))
specFilehandle.close()
self.__savedData = True
return True
def saveConfigurationAs(self, shortcut=False):
self.confFilename = None
self.__savedConf = False
self.saveConfiguration()
def saveConfiguration(self):
ddict = self.getValuesDict()
if self.__savedConf and self.confFilename:
filename = self.confFilename
else:
saveDir = PyMcaDirs.outputDir
filters = 'Sum Rules Analysis files (*.sra);;All files (*.*)'
filename = qt.QFileDialog.getSaveFileName(self,
'Save Sum Rule Analysis Configuration',
saveDir,
filters)
if len(filename) == 0:
return False
else:
filename = str(filename)
if not filename.endswith('.sra'):
filename += '.sra'
self.confFilename = filename
self.__savedConf = False
confDict = ConfigDict.ConfigDict(self.getValuesDict())
try:
confDict.write(filename)
except IOError:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Analysis Error')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('Unable to write configuration to \'%s\''%filename)
msg.exec_()
return False
self.__savedConf = True
return True
def loadConfiguration(self):
confDict = ConfigDict.ConfigDict()
loadDir = PyMcaDirs.outputDir
filters = 'Sum Rules Analysis files (*.sra);;All files (*.*)'
filename = qt.QFileDialog.getOpenFileName(self,
'Load Sum Rule Analysis Configuration',
loadDir,
filters)
if type(filename) in [type(list()), type(tuple())]:
if len(filename):
filename = filename[0]
if len(filename) == 0:
return
else:
filename = str(filename)
try:
confDict.read(filename)
except IOError:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Analysis Error')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('Unable to read configuration file \'%s\''%filename)
return
try:
self.setValuesDict(confDict)
#keysLoaded = confDict.keys()
#keysValues = self.valuesDict.keys()
except KeyError as e:
if DEBUG:
print('loadConfiguration -- Key Error in \'%s\''%filename)
print('\tMessage:', e)
else:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Analysis Error')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('Malformed configuration file \'%s\''%filename)
return
self.__savedConf = True
def close(self):
if not self.__savedConf:
msg = qt.QMessageBox()
msg.setWindowTitle('Sum Rules Tool')
msg.setIcon(qt.QMessageBox.Warning)
msg.setText('The configuration has changed!\nAre you shure you want to close the window?')
msg.setStandardButtons(qt.QMessageBox.Cancel | qt.QMessageBox.Discard)
if msg.exec_() == qt.QMessageBox.Cancel:
return
qt.QMainWindow.close(self)
def setElectronConf(self, eConf):
eConf = str(eConf)
if len(eConf) == 0:
self.electronOccupation.setDisabled(True)
else:
self.electronOccupation.setDisabled(False)
# updates the element combo box
self.elementCB.clear()
elementsList = self.elementsDict[eConf]
self.elementCB.addItems(['']+elementsList)
def getElementInfo(self, symbol):
ddict = {}
symbol = str(symbol)
if len(symbol) == 0:
self.valuesDict[self.__tabElem]['info'] = {}
return
try:
ddict = Elements.Element[symbol]
except KeyError:
msg = ('setElement -- \'%s\' not found in '%symbol)
msg += 'Elements.Element dictionary'
print(msg)
# Update valuesDict
self.valuesDict[self.__tabElem]['info'] = ddict
# Update the EdgeCBs
# Lookup all keys ending in 'xrays'
keys = [item for item in ddict.keys() if item.endswith('xrays')]
keys.sort()
# keys is list of list, flatten it..
transitions = sum([ddict[key] for key in keys],[])
# Only take transitions that occur in the experiment
transitions = [t for t in transitions if t in self.occuringTransitions]
tmpDict = dict( [(transition, ddict[transition]['energy']) for transition in transitions])
for cb, ed in [(self.edge1CB, self.edge1Line),
(self.edge2CB, self.edge2Line)]:
curr = cb.currentText()
cb.clear()
ed.clear()
ed.updateDict(tmpDict)
cb.addItems(['']+transitions)
# Try to set to old entry
idx = cb.findText(QString(curr))
if idx < 0: idx = 0
cb.setCurrentIndex(idx)
def getCurrentTab(self):
idx = self.tabWidget.currentIndex()
return self.tabList[idx]
def getValuesDict(self):
ddict = {}
for tab, tabDict in self.valuesDict.items():
if tab not in ddict.keys():
ddict[tab] = {}
for key, obj in tabDict.items():
value = None
if isinstance(obj, MarkerSpinBox):
value = obj.value()
elif isinstance(obj, qt.QCheckBox):
state = obj.checkState()
if state == qt.Qt.Checked:
value = True
else:
# Also covers state == qt.Qt.PartiallyChecked
value = False
elif isinstance(obj, qt.QComboBox):
tmp = obj.currentText()
value = str(tmp)
elif isinstance(obj, LineEditDisplay) or\
isinstance(obj, qt.QLineEdit):
tmp = str(obj.text())
try:
value = float(tmp)
except ValueError:
value = tmp
elif isinstance(obj, qt.QDoubleSpinBox):
value = obj.value()
elif isinstance(obj, dict):
value = obj
ddict[tab][key] = value
return ddict
def setValuesDict(self, ddict):
markerList = (self.xasMarkerList + self.xmcdMarkerList)
elementList = (self.transitionMetals
+ self.rareEarths
+ self.electronConfs)
# Check as early as possible if element symbol is present
try:
symbol = ddict[self.__tabElem]['element']
self.getElementInfo(symbol)
except KeyError:
pass
for tab, tabDict in ddict.items():
if tab not in self.valuesDict.keys():
raise KeyError('setValuesDict -- Tab not found')
for key, value in tabDict.items():
if not isinstance(key, str):
raise KeyError('setValuesDict -- Key is not str instance')
obj = self.valuesDict[tab][key]
if isinstance(obj, MarkerSpinBox):
try:
tmp = float(value)
obj.setValue(tmp)
except ValueError:
if hasattr(self.plotWindow,'graph'):
xmin, xmax = self.plotWindow.graph.getX1AxisLimits()
else:
xmin, xmax = self.plotWindow.getGraphXLimits()
tmp = xmin + (xmax-xmin)/10.
if DEBUG:
msg = 'setValuesDict -- Float conversion failed'
msg += ' while setting marker positions. Value:', value
print(msg)
elif isinstance(obj, qt.QCheckBox):
if value == 'True':
state = qt.Qt.Checked
else:
state = qt.Qt.Unchecked
obj.setCheckState(state)
elif isinstance(obj, qt.QDoubleSpinBox):
try:
tmp = float(value)
obj.setValue(tmp)
except ValueError:
if DEBUG:
msg = 'setValuesDict -- Float conversion failed'
msg += ' while setting QDoubleSpinBox value. Value:', value
print(msg)
elif isinstance(obj, qt.QComboBox):
idx = obj.findText(QString(value))
obj.setCurrentIndex(idx)
elif isinstance(obj, LineEditDisplay):
# Must be before isinstance(obj, qt.QLineEdit)
# since LineEditDisplay inherits from QLineEdit
obj.checkController()
elif isinstance(obj, qt.QLineEdit):
if value:
tmp = str(value)
obj.setText(tmp)
else:
obj.setText('???')
elif isinstance(obj, dict):
obj = value
else:
raise KeyError('setValuesDict -- \'%s\' not found'%key)
# In case electron shell is set after element..
try:
symbol = ddict[self.__tabElem]['element']
cb = self.valuesDict[self.__tabElem]['element']
idx = cb.findText(QString(symbol))
cb.setCurrentIndex(idx)
except KeyError:
pass
def setRawData(self, x, y, identifier):
if identifier not in ['xmcd', 'xas']:
msg = 'Identifier must either be \'xmcd\' or \'xas\''
raise ValueError(msg)
# Sort energy range
sortedIdx = x.argsort()
xSorted = x.take(sortedIdx)[:]
ySorted = y.take(sortedIdx)[:]
# Ensure strictly monotonically increasing energy range
dx = numpy.diff(x)
if not numpy.all(dx > 0.):
mask = numpy.nonzero(dx)
xSorted = numpy.take(xSorted, mask)
ySorted = numpy.take(ySorted, mask)
# Add spectrum to plotWindow using the
if identifier == 'xmcd':
self.xmcdData = (xSorted, ySorted)
#self.plotWindow.graph.mapToY2(intLegend)
elif identifier == 'xas':
self.xasData = (xSorted, ySorted)
# Trigger replot when data is added
currIdx = self.tabWidget.currentIndex()
self._handleTabChangedSignal(currIdx)
def estimate(self):
tab = self.getCurrentTab()
if tab == self.__tabBG:
self.estimatePrePostEdgePositions()
elif tab == self.__tabInt:
self.estimateInt()
else:
# Do nothing
pass
return
def estimatePrePostEdgePositions(self):
if self.xasData is None:
return
ddict = self.getValuesDict()
edgeList = [ddict[self.__tabElem]['edge1Energy'],
ddict[self.__tabElem]['edge2Energy']]
filterEdgeList = lambda inp:\
float(inp.replace('meV',''))\
if (len(inp)>0 and inp!='---')\
else 0.0
# Use list comprehension instead of map(filterEdgeList, edgeList)
edgeList = [filterEdgeList(edge) for edge in edgeList]
x, y = self.xasData
xLimMin, xLimMax = self.plotWindow.getGraphXLimits()
xMin = x[0]
xMax = x[-1]
xLen = xMax - xMin
xMiddle = .5 *(xMax + xMin)
# Average step length (Watch out for uneven data!)
xStep = (xMax + xMin) / float(len(x))
# Look for the index closest to the physical middle
mask = numpy.nonzero(x <= xMiddle)[0]
idxMid = mask[-1]
factor = 10./100.
edge1, edge2 = edgeList
if edge1 == 0.:
edge1 = xMin + 0.4 * (xMax - xMin)
if edge2 == 0.:
edge2 = xMin + 0.6 * (xMax - xMin)
maxEdge = max(edge1, edge2)
minEdge = min(edge1, edge2)
preMax = minEdge - factor*xLen
postMin = maxEdge + factor*xLen
ddict[self.__tabBG][self.__preMin] = max(xMin,xLimMin+xStep)
ddict[self.__tabBG][self.__preMax] = preMax
ddict[self.__tabBG][self.__postMin] = postMin
ddict[self.__tabBG][self.__postMax] = min(xMax,xLimMax-xStep)
ddict[self.__tabBG]['Edge 1'] = edge1
ddict[self.__tabBG]['Edge 2'] = edge2
self.setValuesDict(ddict)
self.estimateBG()
def estimateInt(self):
if self.xasDataCorr is None or\
self.xasInt is None or\
self.xmcdInt is None:
# Nothing to do...
return
ddict = self.getValuesDict()
x, y = self.xasData
xMin = x[0]
xMax = x[-1]
xLen = xMax - xMin
factor = 10./100.
postMin = ddict[self.__tabBG][self.__postMin]
postMax = ddict[self.__tabBG][self.__postMax]
edge1 = ddict[self.__tabBG]['Edge 1']
edge2 = ddict[self.__tabBG]['Edge 2']
# Estimate intP
if edge1 == 0.:
intP = edge2 + factor * xLen
elif edge2 == 0.:
intP = edge1 + factor * xLen
else:
intP = min(edge1, edge2) + factor * xLen
# Estimate intQ
intQ = postMin + factor * xLen
# Estimate intR
intR = postMax - factor * xLen
# Also estimate the p, q, r Markers:
ddict[self.__tabInt][self.__intP] = intP
ddict[self.__tabInt][self.__intQ] = intQ
ddict[self.__tabInt][self.__intR] = intR
self.setValuesDict(ddict)
def estimateBG(self): # Removed default parameter val=None
if self.xasData is None:
return
if self.tabWidget.currentIndex() != 1:
# Only call from tab 1
return
x, y = self.xasData
ddict = self.getValuesDict()
x01 = ddict[self.__tabBG]['Edge 1']
x02 = ddict[self.__tabBG]['Edge 2']
preMin = ddict[self.__tabBG][self.__preMin]
preMax = ddict[self.__tabBG][self.__preMax]
postMin = ddict[self.__tabBG][self.__postMin]
postMax = ddict[self.__tabBG][self.__postMax]
width = ddict[self.__tabBG]['Step Width']
ratio = ddict[self.__tabBG]['Step Ratio']
if preMin > preMax:
tmp = preMin
preMin = preMax
preMax = tmp
if postMin > postMax:
tmp = preMin
preMin = preMax
preMax = tmp
idxPre = numpy.nonzero((preMin <= x) & (x <= preMax))[0]
idxPost = numpy.nonzero((postMin <= x) & (x <= postMax))[0]
if (len(idxPre) == 0) or (len(idxPost) == 0):
if DEBUG:
print('estimateBG -- Somethings wrong with pre/post edge markers')
return
xPreMin = x[idxPre.min()]
xPreMax = x[idxPre.max()]
xPostMin = x[idxPost.min()]
xPostMax = x[idxPost.max()]
gap = abs(xPreMax - xPostMin)
avgPre = numpy.average(y[idxPre])
avgPost = numpy.average(y[idxPost])
bottom = min(avgPre,avgPost)
top = max(avgPre,avgPost)
if avgPost >= avgPre:
sign = 1.
erf = upstep
else:
sign = -1.
erf = downstep
diff = abs(avgPost - avgPre)
if x02 < x01:
par1 = (ratio, x02, width)
par2 = ((1.-ratio), x01, width)
if DEBUG:
print('estimateBG -- x02 < x01, using par1: %s and par2: %s'\
%(str(par1),str(par2)))
model = bottom + sign * diff * (erf(par1, x) + erf(par2, x))
else:
par1 = (ratio, x01, width)
par2 = ((1.-ratio), x02, width)
if DEBUG:
print('estimateBG -- x01 < x02, using par1: %s and par2: %s'\
%(str(par1),str(par2)))
model = bottom + sign * diff * (erf(par1, x) + erf(par2, x))
preModel = numpy.asarray(len(x)*[avgPre])
postModel = numpy.asarray(len(x)*[avgPost])
self.xasDataBG = x, model
self.plotWindow.addCurve(x,
model,
self.__xasBGmodel,
{},
replot=False)
self.plotWindow.addCurve(x,
preModel,
'Pre BG model',
{},
replot=False)
self.plotWindow.addCurve(x,
postModel,
'Post BG model',
{},
replot=False)
if hasattr(self.plotWindow, 'graph'):
self.plotWindow.graph.replot()
else:
self.plotWindow.replot()
self.plotWindow.updateLegends()
def plotOnDemand(self, window):
# Remove all curves
if hasattr(self.plotWindow,'graph'):
legends = self.plotWindow.getAllCurves(just_legend=True)
for legend in legends:
self.plotWindow.removeCurve(legend, replot=False)
else:
self.plotWindow.clearCurves()
if (self.xmcdData is None) or (self.xasData is None):
# Nothing to do
return
xyList = []
mapToY2 = False
window = window.lower()
if window == self.__tabElem:
if self.xmcdCorrData is not None:
if DEBUG == 1:
print('plotOnDemand -- __tabElem: Using self.xmcdCorrData')
xmcdX, xmcdY = self.xmcdCorrData
xmcdLabel = 'xmcd corr'
else:
if DEBUG == 1:
print('plotOnDemand -- __tabElem: Using self.xmcdData')
xmcdX, xmcdY = self.xmcdData
xmcdLabel = 'xmcd'
xasX, xasY = self.xasData
xyList = [(xmcdX, xmcdY, xmcdLabel, {'plot_yaxis': 'right'}),
(xasX, xasY, 'xas', {})]
# At least one of the curve is going
# to get plotted on secondary y axis
mapToY2 = True
elif window == self.__tabBG:
xasX, xasY= self.xasData
xyList = [(xasX, xasY, 'xas', {})]
if self.xasDataBG is not None:
xasBGX, xasBGY = self.xasDataBG
xyList += [(xasBGX, xasBGY, self.__xasBGmodel, {})]
elif window == self.__tabInt:
if self.xasDataBG is None:
self.xmcdInt = None
self.xasInt = None
return
# Calculate xasDataCorr
xBG, yBG = self.xasDataBG
x, y = self.xasData
self.xasDataCorr = x, y-yBG
if self.xmcdCorrData is not None:
if DEBUG:
print('plotOnDemand -- __tabInt: Using self.xmcdCorrData')
xmcdX, xmcdY = self.xmcdCorrData
xmcdIntLabel = 'xmcd corr Int'
else:
if DEBUG:
print('plotOnDemand -- __tabInt: Using self.xmcdData')
xmcdX, xmcdY = self.xmcdData
xmcdIntLabel = 'xmcd Int'
mathObj = Calculations()
xasX, xasY = self.xasDataCorr
xmcdIntY = mathObj.cumtrapz(y=xmcdY, x=xmcdX)
xmcdIntX = .5 * (xmcdX[1:] + xmcdX[:-1])
xasIntY = mathObj.cumtrapz(y=xasY, x=xasX)
xasIntX = .5 * (xasX[1:] + xasX[:-1])
xyList = [(xmcdIntX, xmcdIntY, xmcdIntLabel, {'plot_yaxis': 'right'}),
(xasX, xasY, 'xas corr', {}),
(xasIntX, xasIntY, 'xas Int', {})]
self.xmcdInt = xmcdIntX, xmcdIntY
self.xasInt = xasIntX, xasIntY
xmin, xmax = numpy.infty, -numpy.infty
ymin, ymax = numpy.infty, -numpy.infty
for x,y,legend,info in xyList:
xmin = min(xmin, x.min())
xmax = max(xmax, x.max())
ymin = min(ymin, y.min())
ymax = max(ymax, y.max())
if DEBUG == 1:
print('plotOnDemand -- adding Curve..')
"""
if mapToY2:
if hasattr(self.plotWindow, 'graph'):
specLegend = self.plotWindow.dataObjectsList[-1]
self.plotWindow.graph.mapToY2(specLegend)
else:
info['plot_yaxis'] = 'right'
"""
self.plotWindow.addCurve(
x=x,
y=y,
legend=legend,
info=info,
replace=False,
replot=True)
# Assure margins in plot when using matplotlibbacken
if not hasattr(self.plotWindow, 'graph'):
if DEBUG == 1:
print('plotOnDemand -- Setting margins..')
print('\txmin:',xmin,'xmax:',xmax)
print('\tymin:',ymin,'ymax:',ymax)
# Pass if no curves present
curves = self.plotWindow.getAllCurves(just_legend=True)
if len(curves) == 0:
# At this point xymin, xymax should be infinite..
pass
xmargin = 0.1 * (xmax - xmin)
ymargin = 0.1 * (ymax - ymin)
self.plotWindow.setGraphXLimits(xmin-xmargin,
xmax+xmargin)
self.plotWindow.setGraphYLimits(ymin-ymargin,
ymax+ymargin)
# Need to force replot here for correct display
self.plotWindow.replot()
self.plotWindow.updateLegends()
def addMarker(self, window, label='X MARKER', xpos=None, unit=''):
# Add spinbox controlling the marker
spinbox = MarkerSpinBox(window, self.plotWindow, label)
# Connects
self.tabChangedSignal.connect(spinbox._setMarkerFollowMouse)
if len(unit) > 0:
text = label + ' ' + unit
else:
text = label
# Widget & Layout
spinboxWidget = qt.QWidget()
spinboxLayout = qt.QHBoxLayout()
spinboxLayout.addWidget(qt.QLabel(text))
spinboxLayout.addWidget(qt.HorizontalSpacer())
spinboxLayout.addWidget(spinbox)
spinboxWidget.setLayout(spinboxLayout)
return spinboxWidget, spinbox
def _handlePlotSignal(self, ddict):
#if 'marker' not in ddict:
if ddict['event'] == 'markerMoved':
if self.tabWidget.currentIndex() == 1: # 1 -> BG tab
self.estimateBG()
def _handleTabChangedSignal(self, idx):
if idx >= len(self.tabList):
print('Tab changed -- Index out of range')
return
tab = self.tabList[idx]
self.plotOnDemand(window=tab)
# Hide/Show markers depending on the selected tab
# Problem: MarkerLabels are stored in markerList,
# however the MarkerSpinBoxes are stores in
# self.valuesDict ...
# edgeMarkers & xasMarkers -> BACKGROUND tab
# xmcdMarker -> INTEGRATION tab
markerList = self.xasMarkerList\
+ self.edgeMarkerList\
+ self.xmcdMarkerList
if tab == self.__tabBG:
self.buttonEstimate.setEnabled(True)
for marker in markerList:
if (marker in self.xasMarkerList) or\
(marker in self.edgeMarkerList):
sb = self.valuesDict[self.__tabBG][marker]
sb.showMarker()
else:
sb = self.valuesDict[self.__tabInt][marker]
sb.hideMarker()
keys = [key for key in self.valuesDict[self.__tabElem].keys()\
if key.endswith('Transition')]
ratioSB = self.valuesDict[self.__tabBG]['Step Ratio']
for idx, keyElem in enumerate(keys):
keyBG = 'Edge %d'%(idx+1)
sb = self.valuesDict[self.__tabBG][keyBG]
parentWidget = sb.parent()
parentWidget.setEnabled(True)
self.estimateBG()
elif tab == self.__tabInt:
self.buttonEstimate.setEnabled(True)
for marker in markerList:
if marker in self.xmcdMarkerList:
sb = self.valuesDict[self.__tabInt][marker]
sb.showMarker()
else:
sb = self.valuesDict[self.__tabBG][marker]
#sb.setValue(0.0) # Should be consistent with estimateBG
sb.hideMarker()
self.calcMagneticMoments()
else: # tab == self.__tabElem:
self.buttonEstimate.setEnabled(False)
for marker in markerList:
if marker in self.xmcdMarkerList:
sb = self.valuesDict[self.__tabInt][marker]
else:
sb = self.valuesDict[self.__tabBG][marker]
sb.showMarker()
self.tabChangedSignal.emit(tab)
def keyPressEvent(self, event):
if event.key() == qt.Qt.Key_F2:
# Switch to tab Element
idx = self.tabList.index(self.__tabElem)
self.tabWidget.setCurrentIndex(idx)
elif event.key() == qt.Qt.Key_F3:
# Switch to tab Background
idx = self.tabList.index(self.__tabBG)
self.tabWidget.setCurrentIndex(idx)
elif event.key() == qt.Qt.Key_F4:
# Switch to tab Integration
idx = self.tabList.index(self.__tabInt)
self.tabWidget.setCurrentIndex(idx)
elif event.key() == qt.Qt.Key_F5:
# Trigger estimation
self.estimate()
else:
qt.QWidget.keyPressEvent(self, event)
[docs]class LoadDichorismDataDialog(qt.QFileDialog):
dataInputSignal = qt.pyqtSignal(object)
def __init__(self, parent=None):
#qt.QDialog.__init__(self, parent)
qt.QFileDialog.__init__(self, parent)
self.dataDict = {}
self.validated = False
self.setWindowTitle('Load Dichorism Data')
if hasattr(self, "setNameFilters"):
self.setNameFilters(['Spec Files (*.spec)',
'Text Files (*.txt; *.dat)',
'All Files (*.*)'])
self.setOption(qt.QFileDialog.DontUseNativeDialog, True)
else:
self.setFilter('Spec Files (*.spec);;'
+'Text Files (*.txt; *.dat);;'
+'All Files (*.*)')
# Take the QSpecFileWidget class as used
# in the main window to select data and
# insert it into a QFileDialog. Emit the
# selected data at acceptance
self.specFileWidget = QSpecFileWidget.QSpecFileWidget(
parent=parent,
autoreplace=False)
# Hide the widget containing the Auto Add/Replace
# checkboxes
self.specFileWidget.autoAddBox.parent().hide()
# Remove the tab widget, only the counter widget
# is needed. Remember: close() only hides a widget
# however the widget persists in the memory.
#self.specFileWidget.mainTab.removeTab(1)
self.specFileWidget.mainTab.hide()
#self.counterTab = self.specFileWidget.mainTab.widget(0)
self.specFileWidget.mainLayout.addWidget(self.specFileWidget.cntTable)
self.specFileWidget.cntTable.show()
# Change the table headers in cntTable
# Note: By conicidence, the original SpecFileCntTable
# has just enough columns as we need. Here, we rename
# the last two:
# 'y' -> 'XAS'
# 'mon' -> 'XMCD'
labels = ['Counter', 'X', 'XAS', 'XMCD']
table = self.specFileWidget.cntTable
for idx in range(len(labels)):
item = table.horizontalHeaderItem(idx)
if item is None:
item = qt.QTableWidgetItem(labels[idx],
qt.QTableWidgetItem.Type)
item.setText(labels[idx])
table.setHorizontalHeaderItem(idx,item)
# Hide the widget containing the Add, Replace, ...
# PushButtons
self.specFileWidget.buttonBox.hide()
# Change selection behavior/mode in the scan list so
# that only a single scan can be selected at a time
self.specFileWidget.list.setSelectionBehavior(qt.QAbstractItemView.SelectRows)
self.specFileWidget.list.setSelectionMode(qt.QAbstractItemView.SingleSelection)
# Tinker with the native layout of QFileDialog
mainLayout = self.layout()
mainLayout.addWidget(self.specFileWidget, 0, 4, 4, 1)
#
# Signals
#
self.currentChanged.connect(self.setDataSource)
def setDataSource(self, filename):
# Opens a spec file and allows to browse its
# contents in the top right widget
filename = str(filename)
if osPathIsDir(filename):
if DEBUG == 1:
print('LoadDichorismDataDialog.setDataSource -- Invalid path or filename..')
return
try:
src = SpecFileDataSource.SpecFileDataSource(filename)
except ValueError:
return
self.specFileWidget.setDataSource(src)
def accept(self):
llist = self.selectedFiles()
if len(llist) == 1:
filename = str(llist[0])
else:
return
self.processSelectedFile(filename)
if self.validated:
super(LoadDichorismDataDialog, self).accept()
def processSelectedFile(self, filename):
self.dataDict = {}
filename = str(filename)
scanList = self.specFileWidget.list.selectedItems()
if len(scanList) == 0:
self.errorMessageBox('No scan selected!')
return
else:
scan = scanList[0]
scanNo = str(scan.text(1))
table = self.specFileWidget.cntTable
# ddict['x'] -> 'X'
# ddict['y'] -> 'XAS'
# ddict['m'] -> 'XMCD'
ddict = table.getCounterSelection()
colX = ddict['x']
colXas = ddict['y']
colXmcd = ddict['m']
# Check if only one is selected
if len(colX) != 1:
self.errorMessageBox('Single counter must be set as X')
return
else:
colX = colX[0]
if len(colXas) != 1:
self.errorMessageBox('Single counter must be set as XAS')
return
else:
colXas = colXas[0]
if len(colXmcd) != 1:
self.errorMessageBox('Single counter must be set as XMCD')
return
else:
colXmcd = colXmcd[0]
if colXas == colX:
self.errorMessageBox('X and XAS use the same counter')
return
elif colX == colXmcd:
self.errorMessageBox('X and XMCD use the same counter')
return
elif colXmcd == colXas:
self.errorMessageBox('XAS and XMCD use the same counter')
return
# Extract data
dataObj = self.specFileWidget.data.getDataObject(scanNo)
# data has format (rows, cols) -> (steps, counters)
self.dataDict['fn'] = filename
self.dataDict['x'] = dataObj.data[:, colX]
self.dataDict['xas'] = dataObj.data[:, colXas]
self.dataDict['xmcd'] = dataObj.data[:, colXmcd]
self.validated = True
self.dataInputSignal.emit(self.dataDict)
def errorMessageBox(self, msg):
box = qt.QMessageBox()
box.setWindowTitle('Sum Rules Load Data Error')
box.setIcon(qt.QMessageBox.Warning)
box.setText(msg)
box.exec_()
if __name__ == '__main__':
app = qt.QApplication([])
win = SumRulesWindow()
#r'C:\Users\tonn\lab\datasets\sum_rules\sum_rules_4f_example_EuRhj2Si2'
#win = DataDisplay.PlotWindow()
#xmin, xmax = win.getGraphXLimits()
#win.insertXMarker(50., draggable=True)
#win = LoadDichorismDataDialog()
#x, avgA, avgB, xmcd, xas = getData()
#win.plotWindow.newCurve(x,xmcd, legend='xmcd', xlabel='ene_st', ylabel='zratio', info={}, replot=False, replace=False)
#win.setRawData(x,xmcd, identifier='xmcd')
#win.plotWindow.newCurve(x,xas, legend='xas', xlabel='ene_st', ylabel='zratio', info={}, replot=False, replace=False)
#win.setRawData(x,xas, identifier='xas')
#win = LoadDichorismDataDialog()
win.show()
app.exec_()