Source code for est.gui.e0calculator

"""Tool to display Spectra maps and estimate E0"""

from silx.gui import qt
from silx.utils.enum import Enum
from est.core.types import XASObject
from silx.gui.plot.items.roi import RectangleROI
from silx.gui.plot.tools.roi import RegionOfInterestManager
import functools
import numpy

from est.gui.XasObjectViewer import MapViewer


[docs] class E0ComputationMethod(Enum): """Methods to compute E0""" MEAN = "mean" MEDIAN = "median"
class _ComputationScale(Enum): ALL = "all" CURRENT = "current"
[docs] class E0CalculatorDialog(qt.QDialog): """Dialog to compute E0 mean or median in a ROI""" def __init__(self, parent=None, xas_obj=None): qt.QDialog.__init__(self, parent=parent) if xas_obj is None: raise ValueError("xas_obj should not be None") self._methodToUse = None # set when the user quit the dialog and define to use mean or median # add the E0Calculator self._mainWidget = E0Calculator(parent=self, xas_obj=xas_obj) self.setLayout(qt.QVBoxLayout()) self.layout().addWidget(self._mainWidget) # add the buttons style = qt.QApplication.instance().style() icon = style.standardIcon(qt.QStyle.SP_DialogApplyButton) _buttons = qt.QDialogButtonBox(parent=self) self._useMedian = qt.QPushButton(icon, "use median", self) _buttons.addButton(self._useMedian, qt.QDialogButtonBox.ActionRole) self._useMean = qt.QPushButton(icon, "use mean", self) _buttons.addButton(self._useMean, qt.QDialogButtonBox.ActionRole) _buttons.addButton(qt.QDialogButtonBox.Cancel) self.layout().addWidget(_buttons) # connect signal / slot _buttons.rejected.connect(self.reject) self._useMean.released.connect( functools.partial(self.validateMethodToUse, E0ComputationMethod.MEAN) ) self._useMedian.released.connect( functools.partial(self.validateMethodToUse, E0ComputationMethod.MEDIAN) )
[docs] def validateMethodToUse(self, method): """Define the method to use and close the dialog""" method = E0ComputationMethod.from_value(method) assert method in (None, E0ComputationMethod.MEDIAN, E0ComputationMethod.MEAN) self._methodToUse = method self.accept()
[docs] def getE0(self): if self._methodToUse is None: return None else: return self._mainWidget.getE0(method=self._methodToUse)
[docs] class E0Calculator(qt.QMainWindow): """Interface to compute E0 mean and median values from a ROI""" sigE0Changed = qt.Signal(float) """Signal emitted when E0 is changing""" def __init__(self, parent=None, xas_obj=None): assert isinstance(xas_obj, (XASObject, type(None))) self._xas_obj = None qt.QMainWindow.__init__(self, parent=parent) # map view self._muView = MapViewer(parent=self, keys=("Mu",)) self._muView.menuBar().hide() self._muView.keySelectionDocker.hide() # for now we don't wan't to manage the roi depending on the dimension. self._muView.setPerspectiveVisible(False) self.setCentralWidget(self._muView) self.setWindowFlags(qt.Qt.Widget) # add a ROI self._roiManager = RegionOfInterestManager(self._muView.getPlot()) self._roiManager.setColor("red") # Set the color of ROI self._roi = RectangleROI() self._updateRoi(xas_obj=xas_obj) self._roi.setName("ROI") self._roi.setEditable(True) self._roiManager.addRoi(self._roi) # dock widget to compute E0 self._dockWidget = qt.QDockWidget(parent=self) self._e0CalculationWidget = _E0CalculationWidget(parent=self._dockWidget) self._dockWidget.setWidget(self._e0CalculationWidget) self.addDockWidget(qt.Qt.RightDockWidgetArea, self._dockWidget) self._dockWidget.setAllowedAreas( qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea ) self._dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) # expose scale self.getComputationScale = self._e0CalculationWidget.getComputationScale self.getE0 = self._e0CalculationWidget.getE0 # connect Signal / Slot self._roi.sigRegionChanged.connect(functools.partial(self._updateE0, "ROI")) self._muView.sigFrameChanged.connect(functools.partial(self._updateE0, "frame")) self._e0CalculationWidget.sigComputationScaleChanged.connect( functools.partial(self._updateE0, "scale") ) # set up if xas_obj is not None: self.setXasObject(xas_obj=xas_obj)
[docs] def setXasObject(self, xas_obj, update_roi=True): """ set XasObject to the E0 calculator :param xas_obj: dataset :param bool update_roi: if True, fir the ROI to the XAS map """ self._xas_obj = xas_obj self._muView.setXasObject(xas_obj=self._xas_obj) if update_roi: self._updateRoi(self._xas_obj) # compute E0 for the current roi self._updateE0(origin="init")
[docs] def getXasObject(self): return self._xas_obj
def _updateRoi(self, xas_obj): if xas_obj is not None: dim_1, dim_2 = xas_obj.dim1, xas_obj.dim2 else: dim_1, dim_2 = 100, 100 self._roi.setGeometry(origin=(0, 0), size=(dim_2, dim_1)) def _updateE0(self, origin): """ :param origin: what is the reason of updating E0: * `init`: construction of the widget, we want to run the the first computation * `frame`: the current frame change * `scale`: the scale to compute E0 changed * `ROI`: the region of interest changed :type: str :return: """ assert origin in ("ROI", "frame", "scale", "init") if origin == "frame" and self.getComputationScale() == _ComputationScale.ALL: return if self._xas_obj is None: return if self.getComputationScale() == _ComputationScale.ALL: data = self._xas_obj.spectra.map_to("mu") else: data = self._muView.getActiveImage(just_legend=False).getData() e0 = self._compute0(roi=self._roi, data=data) self._e0CalculationWidget.setE0(e0) def _compute0(self, roi, data): if self._xas_obj is None: return roi_origin = roi.getOrigin() roi_size = roi.getSize() ymin, ymax = int(roi_origin[1]), int(roi_origin[1] + roi_size[1]) xmin, xmax = int(roi_origin[0]), int(roi_origin[0] + roi_size[0]) # clip roi ymin = max(ymin, 0) ymax = min(ymax, self._xas_obj.dim1) xmin = max(xmin, 0) xmax = min(xmax, self._xas_obj.dim2) if data.ndim == 3: # TODO: take into account the dimension displayed roi_data = data[:, ymin:ymax, xmin:xmax] elif data.ndim: roi_data = data[ymin:ymax, xmin:xmax] else: raise ValueError("given data dim %s is not managed" % data.ndim) return { E0ComputationMethod.MEAN: numpy.mean(roi_data), E0ComputationMethod.MEDIAN: numpy.median(roi_data), }
class _E0CalculationWidget(qt.QGroupBox): """Contains all the options to compute E0 and display the mean and the median values""" sigComputationScaleChanged = qt.Signal(str) """Signal emitted when the computation scale change""" def __init__(self, parent): qt.QGroupBox.__init__(self, parent=parent) self.setTitle("E0") self.setLayout(qt.QGridLayout()) # median self.layout().addWidget(qt.QLabel("median", self), 0, 0) self._medianLE = qt.QLineEdit("0", parent=self) self._medianLE.setReadOnly(True) self.layout().addWidget(self._medianLE, 0, 1) # mean self.layout().addWidget(qt.QLabel("mean", self), 1, 0) self._meanLE = qt.QLineEdit("0", parent=self) self._meanLE.setReadOnly(True) self.layout().addWidget(self._meanLE, 1, 1) # calculation scale option self._computationScale = _E0CalculationScale(parent=self) self.layout().addWidget(self._computationScale, 2, 0, 1, 2) # spacer self._spacer = qt.QWidget(parent=self) self._spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding) self.layout().addWidget(self._spacer, 3, 0) # expose API self.getComputationScale = self._computationScale.getComputationScale # connect signal / slot self._computationScale.sigComputationScaleChanged.connect( self._computationScaleChanged ) def getE0(self, method): """ Return the current E0 value from the requested computation method :param method: method of compuation to get E0 :type: Union[str,ComputationMethod] :return: E0 :rtype: float """ method = E0ComputationMethod.from_value(method) if method is E0ComputationMethod.MEAN: return float(self._meanLE.text()) elif method is E0ComputationMethod.MEDIAN: return float(self._medianLE.text()) else: raise ValueError("Given method %s is not managed" % method) def setE0(self, values): """ :param values: dictionary with the different ComputationMethod as keys and the value computed as values :type: dict :return: """ assert E0ComputationMethod.MEDIAN in values assert E0ComputationMethod.MEAN in values self._meanLE.setText("%.3f" % values[E0ComputationMethod.MEAN]) self._medianLE.setText("%.3f" % values[E0ComputationMethod.MEDIAN]) def _computationScaleChanged(self, scale): self.sigComputationScaleChanged.emit(scale) class _E0CalculationScale(qt.QGroupBox): """Interface to allow the user to select a the scale (from one frame or all frames) to compute E0""" sigComputationScaleChanged = qt.Signal(str) """Signal emitted when the computation scale change""" def __init__(self, parent): qt.QGroupBox.__init__(self, parent) self.__buttonGrp = qt.QButtonGroup(parent=self) self.setLayout(qt.QVBoxLayout()) self.setTitle("compute E0 from:") self._fromCurrentFrameRB = qt.QRadioButton("from current frame", parent=self) self.layout().addWidget(self._fromCurrentFrameRB) self.__buttonGrp.addButton(self._fromCurrentFrameRB) self._fromAllMapsRB = qt.QRadioButton("from all frames", parent=self) self.layout().addWidget(self._fromAllMapsRB) self.__buttonGrp.addButton(self._fromAllMapsRB) # set up self.__buttonGrp.setExclusive(True) self._fromCurrentFrameRB.setChecked(True) # signal / slot connection self.__buttonGrp.buttonReleased.connect(self._statusChanged) def _statusChanged(self, *arg, **kwargs): self.sigComputationScaleChanged.emit(self.getComputationScale().value) def getComputationScale(self): if self._fromAllMapsRB.isChecked(): return _ComputationScale.ALL elif self._fromCurrentFrameRB.isChecked(): return _ComputationScale.CURRENT else: raise ValueError("At least all frame or current frame should be active")