Source code for PyMca5.PyMcaGui.physics.xrf.StrategyHandler

#/*##########################################################################
#
# The PyMca X-Ray Fluorescence Toolkit
#
# Copyright (c) 2004-2014 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__ = "V. Armando Sole - ESRF Data Analysis"
__contact__ = "sole@esrf.fr"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
import sys
import copy
from PyMca5.PyMcaGui import PyMcaQt as qt
from PyMca5.PyMcaGui import PyMcaFileDialogs
from PyMca5.PyMcaPhysics import Elements
from PyMca5.PyMcaGui import PyMca_Icons
from PyMca5.PyMcaIO import ConfigDict
from .MaterialEditor import MaterialComboBox

IconDict = PyMca_Icons.IconDict
QTVERSION = qt.qVersion()
DEBUG = 0

def _getPeakList(fitConfiguration):
    elementsList = []
    for element in fitConfiguration['peaks']:
        if len(element) > 1:
            ele = element[0:1].upper() + element[1:2].lower()
        else:
            ele = element.upper()
        if type(fitConfiguration['peaks'][element]) == type([]):
            for peak in fitConfiguration['peaks'][element]:
                elementsList.append(ele + " " + peak)
        else:
            for peak in [fitConfiguration['peaks'][element]]:
                elementsList.append(ele + " " + peak)
    elementsList.sort()
    return elementsList

def _getMatrixDescription(fitConfiguration):
    useMatrix = False
    detector = None
    for attenuator in list(fitConfiguration['attenuators'].keys()):
        if not fitConfiguration['attenuators'][attenuator][0]:
            # set to be ignored
            continue
        if attenuator.upper() == "MATRIX":
            if fitConfiguration['attenuators'][attenuator][0]:
                useMatrix = True
                matrix = fitConfiguration['attenuators'][attenuator][1:4]
                alphaIn= fitConfiguration['attenuators'][attenuator][4]
                alphaOut= fitConfiguration['attenuators'][attenuator][5]
            else:
                useMatrix = False
            break
    if not useMatrix:
        raise ValueError("Sample matrix has to be specified!")

    if matrix[0].upper() == "MULTILAYER":
        multilayerSample = {}
        layerKeys = list(fitConfiguration['multilayer'].keys())
        if len(layerKeys):
            layerKeys.sort()
        for layer in layerKeys:
            if fitConfiguration['multilayer'][layer][0]:
                multilayerSample[layer] = \
                                fitConfiguration['multilayer'][layer][1:]
    else:
        multilayerSample = {"Auto":matrix}
    return multilayerSample

[docs]class StrategyHandlerWidget(qt.QWidget): sigStrategyHandlerSignal = qt.pyqtSignal(object) def __init__(self, parent=None, name="Single Layer Matrix Iteration Strategy"): qt.QWidget.__init__(self, parent) self._fitConfiguration = None self.setWindowTitle(name) self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self._descriptionButton = qt.QPushButton(self) self._descriptionButton.setText("Hide algorithm description") self._descriptionButton.setAutoDefault(False) self._descriptionButton.clicked.connect(self.toggleDescription) self._descriptionWidget = qt.QTextEdit(self) self._description = qt.QTextDocument() self.mainLayout.addWidget(self._descriptionButton) self.mainLayout.addWidget(self._descriptionWidget) self.build()
[docs] def toggleDescription(self): if self._descriptionButton.text().startswith("Hide"): self._descriptionWidget.hide() self._descriptionButton.setText("Show algorithm description") else: self._descriptionWidget.show() self._descriptionButton.setText("Hide algorithm description")
[docs] def setDescription(self, txt): self._description.setPlainText(txt) self._descriptionWidget.setDocument(self._description)
[docs] def build(self): self.strategy = {} self.strategy["SingleLayerStrategy"] = SingleLayerStrategyWidget(self) currentStrategy = self.strategy["SingleLayerStrategy"] self.setDescription(currentStrategy.getDescription()) self.mainLayout.addWidget(currentStrategy)
[docs] def setFitConfiguration(self, fitConfiguration): self._fitConfiguration = copy.deepcopy(fitConfiguration) strategy = self._fitConfiguration["fit"].get("strategy", "SingleLayerStrategy") self.strategy[strategy].setFitConfiguration(self._fitConfiguration)
[docs] def getParameters(self): if self._fitConfiguration is None: return {} strategy = self._fitConfiguration["fit"].get("strategy", "SingleLayerStrategy") return {strategy:self.strategy[strategy].getParameters()}
[docs] def setParameters(self, ddict):
# this is used to use the current fit configuration but with other strategy configuration # from other file if self._fitConfiguration is None: return strategy = self._fitConfiguration["fit"].get("strategy", "SingleLayerStrategy") if strategy in ddict: return self.strategy[strategy].setParameters(ddict[strategy])
[docs]class SingleLayerStrategyWidget(qt.QWidget): def __init__(self, parent=None, name="Single Layer Matrix Iteration Strategy"): qt.QWidget.__init__(self, parent) self.setWindowTitle(name) self.build()
[docs] def getDescription(self): txt = "WARNING: Not recommended for use with internal standard if the " txt += "internal standard is present in the refining layer. You will " txt += "get better results working in fundamental parameters mode.\n" txt += "This matrix iteration procedure is implemented as follows:\n" txt += "The concentration of the elements selected to be updated, will " txt += "be incorporated in the matrix in the specified form.\n" txt += "If the sum of the mass fractions of those elements is above 1 " txt += "the program will normalize as usual.\n" txt += "If the sum of the mass fractions is below 1, the same procedure " txt += "will be applied unless the user has chosen a completing material.\n" txt += "Limitations of the algorithm:\n" txt += "- The incorporated elements cannot be on different layers.\n" txt += "- One element cannot be selected more than once.\n" txt += "Recommendations:\n" txt += "- In order to avoid unnecessarily slow setups, " txt += "activate this option and any secondary or tertiary excitation " txt += "calculation once you are ready for quantification." return txt
[docs] def build(self): self.mainLayout = qt.QGridLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) label = qt.QLabel("Number of matrix iterations to perfom:") self._nIterations = qt.QSpinBox(self) self._nIterations.setMinimum(1) self._nIterations.setMaximum(5) self._nIterations.setValue(3) self.mainLayout.addWidget(label, 0, 0) self.mainLayout.addWidget(qt.HorizontalSpacer(self), 1, 0) self.mainLayout.addWidget(self._nIterations, 0, 2) label = qt.QLabel("Layer in wich the algorithm is to be applied:") self._layerOptions = qt.QComboBox(self) self._layerOptions.addItem("Auto") self.mainLayout.addWidget(label, 1, 0) #self.mainLayout.addWidget(qt.HorizontalSpacer(self), 1, 0) self.mainLayout.addWidget(self._layerOptions, 1, 2) label = qt.QLabel("Completing material to be used:") materialList = list(Elements.Material.keys()) materialList.sort() a = ["-"] for key in materialList: a.append(key) self._materialOptions = MyQComboBox(self, options=a) self._materialOptions.addItem("-") self.mainLayout.addWidget(label, 2, 0) self.mainLayout.addWidget(self._materialOptions, 2, 2) self._table = IterationTable(self) self.mainLayout.addWidget(self._table, 3, 0, 5, 5) self.mainLayout.addWidget(qt.VerticalSpacer(self), 10, 0)
[docs] def setFitConfiguration(self, fitConfiguration):
# obtain the peak families fitted _peakList = _getPeakList(fitConfiguration) if not len(_peakList): raise ValueError("No peaks to fit!!!!") matrixDescription = _getMatrixDescription(fitConfiguration) layerList = list(matrixDescription.keys()) layerList.sort() materialList = list(Elements.Material.keys()) materialList.sort() a = ["-"] for key in materialList: a.append(key) # Material options self._materialOptions.setOptions(a) self._table.setMaterialOptions(a) # If only one layer, all the elements are selectable layerPeaks = {} if len(layerList) == 1: layerPeaks[layerList[0]] = _peakList else: inAllLayers = [] toDeleteFromAllLayers = [] toForgetAbout = [] for layer in layerList: layerPeaks[layer] = [] for peak in _peakList: element = peak.split()[0] alreadyInSomeLayer = False presentInLayer = "" toDeleteFromAllLayers = False for layer in layerList: material = matrixDescription[layer][0] if element in Elements.getMaterialMassFractions([material], [1.0]): if alreadyInSomeLayer: toDeleteFromAllLayers = True else: alreadyInSomeLayer = True presentInLayer = layer if toDeleteFromAllLayers: continue if not alreadyInSomeLayer: for layer in layerList: layerPeaks[layer].append(peak) else: layerPeaks[presentInLayer].append(peak) oldOption = self._layerOptions.currentText() self._layerOptions.clear() for item in layerList: self._layerOptions.addItem(item) self._layerList = layerList if oldOption not in layerList: oldOption = layerList[0] self._layerOptions.setCurrentIndex(layerList.index(oldOption)) self._layerList = layerList self._layerPeaks = layerPeaks self._table.setLayerPeakFamilies(layerPeaks[oldOption]) strategy = fitConfiguration["fit"].get("strategy", "SingleLayerStrategy") if strategy in fitConfiguration: self.setParameters(fitConfiguration["SingleLayerStrategy"])
[docs] def getParameters(self): ddict = self._table.getParameters() ddict["layer"] = str(self._layerOptions.currentText()) ddict["iterations"] = self._nIterations.value() ddict["completer"] = str(self._materialOptions.currentText()) return ddict
[docs] def setParameters(self, ddict): layer = ddict.get("layer", "Auto") if layer not in self._layerList: if layer.upper() != "AUTO": raise ValueError("Layer %s not among fitted layers" % layer) else: layerList = self._layerList + ["Auto"] self._layerOptions.clear() for item in layerList: self._layerOptions.addItem(item) self._layerList = layerList nIterations = ddict.get("iterations", 3) self._nIterations.setValue(nIterations) layerList = self._layerList layerPeaks = self._layerPeaks self._layerOptions.setCurrentIndex(layerList.index(layer)) if layer in layerPeaks: self._table.setLayerPeakFamilies(layerPeaks[layer]) completer = ddict.get("completer", "-") self._materialOptions.setCurrentText(completer) flags = ddict["flags"] families = ddict["peaks"] materials = ddict["materials"] nItem = 0 for i in range(len(flags)): doIt = 0 if (flags[i] in [1, True, "1", "True"]) and (layer in layerPeaks): flag = 1 if families[i] in layerPeaks[layer]: if materials[i] in ["-"]: doIt = 1 else: element = families[i].split()[0] if element in Elements.getMaterialMassFractions( \ [materials[i]], [1.0]): doIt = 1 if doIt: self._table.setData(nItem, flag, families[i], materials[i]) else: self._table.setData(nItem, flag, families[i], element) else: self._table.setData(nItem, 0, "-", "-") nItem += 1
[docs]class IterationTable(qt.QTableWidget): sigValueChanged = qt.pyqtSignal(int, int) def __init__(self, parent=None): qt.QTableWidget.__init__(self, parent) self.verticalHeader().hide() self.setRowCount(5) self.setColumnCount(6) labels = ["Use", "Peak Family", "Material Form"] * 2 for i in range(len(labels)): item = self.horizontalHeaderItem(i) if item is None: item = qt.QTableWidgetItem(labels[i], qt.QTableWidgetItem.Type) self.setHorizontalHeaderItem(i,item) self.build() self.resizeColumnToContents(0) self.resizeColumnToContents(3) self.cellChanged[int, int].connect(self.mySlot)
[docs] def setData(self, idx, use, peak, material="-"): row = idx % 5 c = 3 * (idx // self.rowCount()) item = self.cellWidget(row, 0 + c) if use: item.setChecked(True) else: item.setChecked(False) item = self.cellWidget(row, 1 + c) n = item.findText(peak) item.setCurrentIndex(n) ddict = {} ddict['row'] = row ddict['col'] = 1 + c ddict['text'] = peak self.__updateMaterialOptions(ddict) item = self.cellWidget(row, 2 + c) item.setEditText(material)
[docs] def mySlot(self,row,col): if DEBUG: print("Value changed row = %d col = %d" % (row, col)) if col != 0: print("Text = %s" % self.cellWidget(row, col).currentText())
def _checkBoxSlot(self, ddict): # check we do not have duplicates row = ddict['row'] col = ddict['col'] target = str(self.cellWidget(row, 1 + col).currentText()).split()[0] for idx in range(10): r = idx % 5 c = 3 * (idx // self.rowCount()) if r == row: if c == col: continue item = self.cellWidget(r, 0 + c) if item.isChecked(): element = str(self.cellWidget(r, 1 + c).currentText()).split()[0] if target == element: # reset the just changed one self.cellWidget(row, col).setChecked(False) self.cellWidget(row, col + 1).setCurrentIndex(0) self.cellWidget(row, col + 2).setCurrentText("-") return self.setCurrentCell(row, col) self.sigValueChanged.emit(row, col)
[docs] def build(self): materialList = list(Elements.Material.keys()) materialList.sort() a = ["-"] for key in materialList: a.append(key) for idx in range(10): row = idx % 5 c = 3 * (idx // self.rowCount()) item = self.cellWidget(row, 0 + c) if item is None: item = MyCheckBox(self, row, 0 + c) self.setCellWidget(row, 0 + c, item) item.sigMyCheckBoxSignal.connect(self._checkBoxSlot) item = self.cellWidget(row, 1 + c) if item is None: item = SimpleComboBox(self, row=row, col=1 + c) self.setCellWidget(row, 1 + c, item) item.sigSimpleComboBoxSignal.connect(self._peakFamilySlot) item = self.cellWidget(row, 2 + c) if item is None: item = MyQComboBox(self, options=a, row=row, col=2 + c) item.setEditable(True) self.setCellWidget(row, 2 + c, item) item.sigMaterialComboBoxSignal.connect(self._comboSlot)
[docs] def setMaterialOptions(self, options): for idx in range(10): row = idx % 5 c = 3 * (idx // self.rowCount()) item = self.cellWidget(row, 2 + c) item.setOptions(options)
[docs] def setLayerPeakFamilies(self, layerPeaks): for idx in range(10): row = idx % 5 c = 3 * (idx // self.rowCount()) item = self.cellWidget(row, 1 + c) item.setOptions(["-"] + layerPeaks) # reset material form item = self.cellWidget(row, 2 + c) item.setCurrentIndex(0)
def __updateMaterialOptions(self, ddict): row = ddict['row'] col = ddict['col'] text = ddict['text'] element = text.split()[0] materialItem = self.cellWidget(row, col + 1) associatedMaterial = str(materialItem.currentText()) goodCandidates = [element] for i in range(materialItem.count()): material = str(materialItem.itemText(i)) if material not in ["-", element]: if element in Elements.getMaterialMassFractions([material], [1.0]): goodCandidates.append(material) materialItem.clear() materialItem.setOptions(goodCandidates) if associatedMaterial in goodCandidates: materialItem.setCurrentIndex(goodCandidates.index(associatedMaterial)) else: materialItem.setCurrentIndex(0) def _peakFamilySlot(self, ddict): if DEBUG: print("_peakFamilySlot", ddict) # check we do not have duplicates target = ddict["text"].split()[0] row = ddict['row'] col = ddict['col'] for idx in range(10): r = idx % 5 c = 3 * (idx // self.rowCount()) if r == row: if (c + 1) == col: continue item = self.cellWidget(r, 0 + c) if item.isChecked(): element = str(self.cellWidget(r, 1 + c).currentText()).split()[0] if target == element: # reset the just changed one self.cellWidget(row, col - 1).setChecked(False) return self.__updateMaterialOptions(ddict) self.setCurrentCell(row, col) self.sigValueChanged.emit(row, col) def _comboSlot(self, ddict): if DEBUG: print("_comboSlot", ddict) row = ddict['row'] col = ddict['col'] text = ddict['text'] self.setCurrentCell(row, col) self.sigValueChanged.emit(row, col)
[docs] def getParameters(self): ddict = {} ddict["flags"] = [] ddict["peaks"] = [] ddict["materials"] = [] for idx in range(10): row = idx % 5 c = 3 * (idx // self.rowCount()) item = self.cellWidget(row, 0 + c) if item.isChecked(): peak = str(self.cellWidget(row, 1 + c).currentText()) if peak in ["-"]: continue #raise ValueError("Invalid peak family in row %d" % row) ddict["flags"].append(1) ddict["peaks"].append(peak) ddict["materials"].append(self.cellWidget(row, 2 + c).currentText()) else: ddict["flags"].append(0) ddict["peaks"].append(self.cellWidget(row, 1 + c).currentText()) ddict["materials"].append(self.cellWidget(row, 2 + c).currentText()) return ddict
[docs]class SimpleComboBox(qt.QComboBox): sigSimpleComboBoxSignal = qt.pyqtSignal(object) def __init__(self, parent=None,row=None, col=None): if row is None: row = 0 if col is None: col = 0 self.row = row self.col = col qt.QComboBox.__init__(self,parent) self.setEditable(False) self.setDuplicatesEnabled(False) self.activated[str].connect(self._mySignal)
[docs] def setOptions(self, options): self.clear() for item in options: self.addItem(item)
def _mySignal(self, txt): ddict = {} ddict["event"] = "activated" ddict["row"] = self.row ddict["col"] = self.col ddict["text"] = self.currentText() self.sigSimpleComboBoxSignal.emit(ddict)
[docs]class MyQComboBox(MaterialComboBox): def _mySignal(self, qstring0): qstring = qstring0 (result, index) = self.ownValidator.validate(qstring, 0) if result != self.ownValidator.Valid: qstring = self.ownValidator.fixup(qstring) (result, index) = self.ownValidator.validate(qstring,0) if result != self.ownValidator.Valid: text = str(qstring) if text.upper() not in ["-", "None"]: qt.QMessageBox.critical(self, "Invalid Material '%s'" % text, "The material '%s' is not a valid Formula " \ "nor a valid Material.\n" \ "Please define the material %s or correct the formula\n" % \ (text, text)) self.setCurrentIndex(0) for i in range(self.count()): selftext = self.itemText(i) if selftext == qstring0: self.removeItem(i) break return text = str(qstring) self.setCurrentText(text) ddict = {} ddict['event'] = 'activated' ddict['row'] = self.row ddict['col'] = self.col ddict['text'] = text if qstring0 != qstring: self.removeItem(self.count() - 1) insert = True for i in range(self.count()): selftext = self.itemText(i) if qstring == selftext: insert = False if insert: self.insertItem(-1, qstring) # signal defined in the superclass. self.sigMaterialComboBoxSignal.emit(ddict)
[docs]class MyCheckBox(qt.QCheckBox): sigMyCheckBoxSignal = qt.pyqtSignal(object) def __init__(self, parent=None, row=0, col=0): qt.QCheckBox.__init__(self, parent) self._row = row self._col = col self.stateChanged[int].connect(self._emitSignal) def _emitSignal(self, *var): ddict = {} ddict["row"] = self._row ddict["col"] = self._col self.sigMyCheckBoxSignal.emit(ddict)
[docs]class StrategyHandlerDialog(qt.QDialog): def __init__(self, parent=None): qt.QDialog.__init__(self, parent) self.setWindowTitle("Fit Strategy Configuration Window") self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(2) self.handlerWidget = StrategyHandlerWidget(self) # mimic behavior self.setFitConfiguration = self.handlerWidget.setFitConfiguration self.getParameters = self.handlerWidget.getParameters self.setParameters = self.handlerWidget.setParameters # the actions hbox = qt.QWidget(self) hboxLayout = qt.QHBoxLayout(hbox) hboxLayout.setContentsMargins(0, 0, 0, 0) hboxLayout.setSpacing(2) self.loadButton = qt.QPushButton(hbox) self.loadButton.setText("Load") self.loadButton.setAutoDefault(False) self.loadButton.setToolTip("Read the strategy parameters from other fit configuration file") self.okButton = qt.QPushButton(hbox) self.okButton.setText("OK") self.okButton.setAutoDefault(False) self.dismissButton = qt.QPushButton(hbox) self.dismissButton.setText("Cancel") self.dismissButton.setAutoDefault(False) hboxLayout.addWidget(self.loadButton) hboxLayout.addWidget(qt.HorizontalSpacer(hbox)) hboxLayout.addWidget(self.okButton) hboxLayout.addWidget(self.dismissButton) hboxLayout.addWidget(qt.HorizontalSpacer(hbox)) # layout self.mainLayout.addWidget(self.handlerWidget) self.mainLayout.addWidget(hbox) # connect self.loadButton.clicked.connect(self.load) self.dismissButton.clicked.connect(self.reject) self.okButton.clicked.connect(self.accept)
[docs] def sizeHint(self): return qt.QSize(int(1.5*qt.QDialog.sizeHint(self).width()), qt.QDialog.sizeHint(self).height())
[docs] def load(self): fileList = PyMcaFileDialogs.getFileList(parent=self, filetypelist=["Fit files (*.cfg)"], message="Select a fit configuration file", mode="OPEN", getfilter=False, single=True) if len(fileList): d = ConfigDict.ConfigDict() d.read(fileList[0]) self.setParameters(d)
[docs]def main(fileName=None): app = qt.QApplication(sys.argv) w = StrategyHandlerDialog() if fileName is not None: d = ConfigDict.ConfigDict() d.read(fileName) d["fit"]["strategy"] = "SingleLayerStrategy" d["SingleLayerStrategy"] = {} d["SingleLayerStrategy"]["iterations"] = 4 d["SingleLayerStrategy"]["flags"] = 1, 1, 0, 1 d["SingleLayerStrategy"]["peaks"] = "Cr K", "Fe K", "Mn K", "Fe Ka" d["SingleLayerStrategy"]["materials"] = "-", "Goethite", "-", "Goethite" d["SingleLayerStrategy"]["completer"] = "Mo" w.setFitConfiguration(d) if w.exec_() == qt.QDialog.Accepted: print(w.getParameters())
if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python StrategyHandler FitConfigurationFile") main() else: fileName = sys.argv[1] print(main(fileName))