Source code for silx.gui.plot.CurvesROIWidget

# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2004-2017 European Synchrotron Radiation Facility
#
# 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.
#
# ###########################################################################*/
"""Widget to handle regions of interest (ROI) on curves displayed in a PlotWindow.

This widget is meant to work with :class:`PlotWindow`.

ROI are defined by :

- A name (`ROI` column)
- A type. The type is the label of the x axis.
  This can be used to apply or not some ROI to a curve and do some post processing.
- The x coordinate of the left limit (`from` column)
- The x coordinate of the right limit (`to` column)
- Raw counts: integral of the curve between the
  min ROI point and the max ROI point to the y = 0 line

  .. image:: img/rawCounts.png

- Net counts: the integral of the curve between the
  min ROI point and the max ROI point to [ROI min point, ROI max point] segment

  .. image:: img/netCounts.png
"""

__authors__ = ["V.A. Sole", "T. Vincent"]
__license__ = "MIT"
__date__ = "27/06/2017"

from collections import OrderedDict

import logging
import os
import sys

import numpy

from silx.io import dictdump
from .. import icons, qt


_logger = logging.getLogger(__name__)


[docs]class CurvesROIWidget(qt.QWidget): """Widget displaying a table of ROI information. :param parent: See :class:`QWidget` :param str name: The title of this widget """ sigROIWidgetSignal = qt.Signal(object) """Signal of ROIs modifications. Modification information if given as a dict with an 'event' key providing the type of events. Type of events: - AddROI, DelROI, LoadROI and ResetROI with keys: 'roilist', 'roidict' - selectionChanged with keys: 'row', 'col' 'roi', 'key', 'colheader', 'rowheader' """ def __init__(self, parent=None, name=None): super(CurvesROIWidget, self).__init__(parent) if name is not None: self.setWindowTitle(name) layout = qt.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) ############## self.headerLabel = qt.QLabel(self) self.headerLabel.setAlignment(qt.Qt.AlignHCenter) self.setHeader() layout.addWidget(self.headerLabel) ############## self.roiTable = ROITable(self) rheight = self.roiTable.horizontalHeader().sizeHint().height() self.roiTable.setMinimumHeight(4 * rheight) self.fillFromROIDict = self.roiTable.fillFromROIDict self.getROIListAndDict = self.roiTable.getROIListAndDict layout.addWidget(self.roiTable) self._roiFileDir = qt.QDir.home().absolutePath() ################# hbox = qt.QWidget(self) hboxlayout = qt.QHBoxLayout(hbox) hboxlayout.setContentsMargins(0, 0, 0, 0) hboxlayout.setSpacing(0) hboxlayout.addStretch(0) self.addButton = qt.QPushButton(hbox) self.addButton.setText("Add ROI") self.addButton.setToolTip('Create a new ROI') self.delButton = qt.QPushButton(hbox) self.delButton.setText("Delete ROI") self.addButton.setToolTip('Remove the selected ROI') self.resetButton = qt.QPushButton(hbox) self.resetButton.setText("Reset") self.addButton.setToolTip('Clear all created ROIs. We only let the default ROI') hboxlayout.addWidget(self.addButton) hboxlayout.addWidget(self.delButton) hboxlayout.addWidget(self.resetButton) hboxlayout.addStretch(0) self.loadButton = qt.QPushButton(hbox) self.loadButton.setText("Load") self.loadButton.setToolTip('Load ROIs from a .ini file') self.saveButton = qt.QPushButton(hbox) self.saveButton.setText("Save") self.loadButton.setToolTip('Save ROIs to a .ini file') hboxlayout.addWidget(self.loadButton) hboxlayout.addWidget(self.saveButton) layout.setStretchFactor(self.headerLabel, 0) layout.setStretchFactor(self.roiTable, 1) layout.setStretchFactor(hbox, 0) layout.addWidget(hbox) self.addButton.clicked.connect(self._add) self.delButton.clicked.connect(self._del) self.resetButton.clicked.connect(self._reset) self.loadButton.clicked.connect(self._load) self.saveButton.clicked.connect(self._save) self.roiTable.sigROITableSignal.connect(self._forward) @property def roiFileDir(self): """The directory from which to load/save ROI from/to files.""" if not os.path.isdir(self._roiFileDir): self._roiFileDir = qt.QDir.home().absolutePath() return self._roiFileDir @roiFileDir.setter def roiFileDir(self, roiFileDir): self._roiFileDir = str(roiFileDir)
[docs] def setRois(self, roidict, order=None): """Set the ROIs by providing a dictionary of ROI information. The dictionary keys are the ROI names. Each value is a sub-dictionary of ROI info with the following fields: - ``"from"``: x coordinate of the left limit, as a float - ``"to"``: x coordinate of the right limit, as a float - ``"type"``: type of ROI, as a string (e.g "channels", "energy") :param roidict: Dictionary of ROIs :param str order: Field used for ordering the ROIs. One of "from", "to", "type". None (default) for no ordering, or same order as specified in parameter ``roidict`` if provided as an OrderedDict. """ if order is None or order.lower() == "none": roilist = list(roidict.keys()) else: assert order in ["from", "to", "type"] roilist = sorted(roidict.keys(), key=lambda roi_name: roidict[roi_name].get(order)) return self.roiTable.fillFromROIDict(roilist, roidict)
[docs] def getRois(self, order=None): """Return the currently defined ROIs, as an ordered dict. The dictionary keys are the ROI names. Each value is a sub-dictionary of ROI info with the following fields: - ``"from"``: x coordinate of the left limit, as a float - ``"to"``: x coordinate of the right limit, as a float - ``"type"``: type of ROI, as a string (e.g "channels", "energy") :param order: Field used for ordering the ROIs. One of "from", "to", "type", "netcounts", "rawcounts". None (default) to get the same order as displayed in the widget. :return: Ordered dictionary of ROI information """ roilist, roidict = self.roiTable.getROIListAndDict() if order is None or order.lower() == "none": ordered_roilist = roilist else: assert order in ["from", "to", "type", "netcounts", "rawcounts"] ordered_roilist = sorted(roidict.keys(), key=lambda roi_name: roidict[roi_name].get(order)) return OrderedDict([(name, roidict[name]) for name in ordered_roilist])
def _add(self): """Add button clicked handler""" ddict = {} ddict['event'] = "AddROI" roilist, roidict = self.roiTable.getROIListAndDict() ddict['roilist'] = roilist ddict['roidict'] = roidict self.sigROIWidgetSignal.emit(ddict) def _del(self): """Delete button clicked handler""" row = self.roiTable.currentRow() if row >= 0: index = self.roiTable.labels.index('Type') text = str(self.roiTable.item(row, index).text()) if text.upper() != 'DEFAULT': index = self.roiTable.labels.index('ROI') key = str(self.roiTable.item(row, index).text()) else: # This is to prevent deleting ICR ROI, that is # usually initialized as "Default" type. return roilist, roidict = self.roiTable.getROIListAndDict() row = roilist.index(key) del roilist[row] del roidict[key] if len(roilist) > 0: currentroi = roilist[0] else: currentroi = None self.roiTable.fillFromROIDict(roilist=roilist, roidict=roidict, currentroi=currentroi) ddict = {} ddict['event'] = "DelROI" ddict['roilist'] = roilist ddict['roidict'] = roidict self.sigROIWidgetSignal.emit(ddict) def _forward(self, ddict): """Broadcast events from ROITable signal""" self.sigROIWidgetSignal.emit(ddict) def _reset(self): """Reset button clicked handler""" ddict = {} ddict['event'] = "ResetROI" roilist0, roidict0 = self.roiTable.getROIListAndDict() index = 0 for key in roilist0: if roidict0[key]['type'].upper() == 'DEFAULT': index = roilist0.index(key) break roilist = [] roidict = {} if len(roilist0): roilist.append(roilist0[index]) roidict[roilist[0]] = {} roidict[roilist[0]].update(roidict0[roilist[0]]) self.roiTable.fillFromROIDict(roilist=roilist, roidict=roidict) ddict['roilist'] = roilist ddict['roidict'] = roidict self.sigROIWidgetSignal.emit(ddict) def _load(self): """Load button clicked handler""" dialog = qt.QFileDialog(self) dialog.setNameFilters( ['INI File *.ini', 'JSON File *.json', 'All *.*']) dialog.setFileMode(qt.QFileDialog.ExistingFile) dialog.setDirectory(self.roiFileDir) if not dialog.exec_(): dialog.close() return # pyflakes bug http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=666494 outputFile = dialog.selectedFiles()[0] dialog.close() self.roiFileDir = os.path.dirname(outputFile) self.load(outputFile)
[docs] def load(self, filename): """Load ROI widget information from a file storing a dict of ROI. :param str filename: The file from which to load ROI """ rois = dictdump.load(filename) currentROI = None if self.roiTable.rowCount(): item = self.roiTable.item(self.roiTable.currentRow(), 0) if item is not None: currentROI = str(item.text()) # Remove rawcounts and netcounts from ROIs for roi in rois['ROI']['roidict'].values(): roi.pop('rawcounts', None) roi.pop('netcounts', None) self.roiTable.fillFromROIDict(roilist=rois['ROI']['roilist'], roidict=rois['ROI']['roidict'], currentroi=currentROI) roilist, roidict = self.roiTable.getROIListAndDict() event = {'event': 'LoadROI', 'roilist': roilist, 'roidict': roidict} self.sigROIWidgetSignal.emit(event)
def _save(self): """Save button clicked handler""" dialog = qt.QFileDialog(self) dialog.setNameFilters(['INI File *.ini', 'JSON File *.json']) dialog.setFileMode(qt.QFileDialog.AnyFile) dialog.setAcceptMode(qt.QFileDialog.AcceptSave) dialog.setDirectory(self.roiFileDir) if not dialog.exec_(): dialog.close() return outputFile = dialog.selectedFiles()[0] extension = '.' + dialog.selectedNameFilter().split('.')[-1] dialog.close() if not outputFile.endswith(extension): outputFile += extension if os.path.exists(outputFile): try: os.remove(outputFile) except IOError: msg = qt.QMessageBox(self) msg.setIcon(qt.QMessageBox.Critical) msg.setText("Input Output Error: %s" % (sys.exc_info()[1])) msg.exec_() return self.roiFileDir = os.path.dirname(outputFile) self.save(outputFile)
[docs] def save(self, filename): """Save current ROIs of the widget as a dict of ROI to a file. :param str filename: The file to which to save the ROIs """ roilist, roidict = self.roiTable.getROIListAndDict() datadict = {'ROI': {'roilist': roilist, 'roidict': roidict}} dictdump.dump(datadict, filename)
[docs] def setHeader(self, text='ROIs'): """Set the header text of this widget""" self.headerLabel.setText("<b>%s<\b>" % text)
[docs]class ROITable(qt.QTableWidget): """Table widget displaying ROI information. See :class:`QTableWidget` for constructor arguments. """ sigROITableSignal = qt.Signal(object) """Signal of ROI table modifications. """ def __init__(self, *args, **kwargs): super(ROITable, self).__init__(*args, **kwargs) self.setRowCount(1) self.labels = 'ROI', 'Type', 'From', 'To', 'Raw Counts', 'Net Counts' self.setColumnCount(len(self.labels)) self.setSortingEnabled(False) for index, label in enumerate(self.labels): item = self.horizontalHeaderItem(index) if item is None: item = qt.QTableWidgetItem(label, qt.QTableWidgetItem.Type) item.setText(label) self.setHorizontalHeaderItem(index, item) self.roidict = {} self.roilist = [] self.building = False self.fillFromROIDict(roilist=self.roilist, roidict=self.roidict) self.cellClicked[(int, int)].connect(self._cellClickedSlot) self.cellChanged[(int, int)].connect(self._cellChangedSlot) verticalHeader = self.verticalHeader() verticalHeader.sectionClicked[int].connect(self._rowChangedSlot) self.__setTooltip() def __setTooltip(self): assert(self.labels[0] == 'ROI') self.horizontalHeaderItem(0).setToolTip('Region of interest identifier') assert(self.labels[1] == 'Type') self.horizontalHeaderItem(1).setToolTip('Type of the ROI') assert(self.labels[2] == 'From') self.horizontalHeaderItem(2).setToolTip('X-value of the min point') assert(self.labels[3] == 'To') self.horizontalHeaderItem(3).setToolTip('X-value of the max point') assert(self.labels[4] == 'Raw Counts') self.horizontalHeaderItem(4).setToolTip('Estimation of the integral \ between y=0 and the selected curve') assert(self.labels[5] == 'Net Counts') self.horizontalHeaderItem(5).setToolTip('Estimation of the integral \ between the segment [maxPt, minPt] and the selected curve')
[docs] def fillFromROIDict(self, roilist=(), roidict=None, currentroi=None): """Set the ROIs by providing a list of ROI names and a dictionary of ROI information for each ROI. The ROI names must match an existing dictionary key. The name list is used to provide an order for the ROIs. The dictionary's values are sub-dictionaries containing 3 mandatory fields: - ``"from"``: x coordinate of the left limit, as a float - ``"to"``: x coordinate of the right limit, as a float - ``"type"``: type of ROI, as a string (e.g "channels", "energy") :param roilist: List of ROI names (keys of roidict) :type roilist: List :param dict roidict: Dict of ROI information :param currentroi: Name of the selected ROI or None (no selection) """ if roidict is None: roidict = {} self.building = True line0 = 0 self.roilist = [] self.roidict = {} for key in roilist: if key in roidict.keys(): roi = roidict[key] self.roilist.append(key) self.roidict[key] = {} self.roidict[key].update(roi) line0 = line0 + 1 nlines = self.rowCount() if (line0 > nlines): self.setRowCount(line0) line = line0 - 1 self.roidict[key]['line'] = line ROI = key roitype = "%s" % roi['type'] fromdata = "%6g" % (roi['from']) todata = "%6g" % (roi['to']) if 'rawcounts' in roi: rawcounts = "%6g" % (roi['rawcounts']) else: rawcounts = " ?????? " if 'netcounts' in roi: netcounts = "%6g" % (roi['netcounts']) else: netcounts = " ?????? " fields = [ROI, roitype, fromdata, todata, rawcounts, netcounts] col = 0 for field in fields: key2 = self.item(line, col) if key2 is None: key2 = qt.QTableWidgetItem(field, qt.QTableWidgetItem.Type) self.setItem(line, col, key2) else: key2.setText(field) if (ROI.upper() == 'ICR') or (ROI.upper() == 'DEFAULT'): key2.setFlags(qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled) else: if col in [0, 2, 3]: key2.setFlags(qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled | qt.Qt.ItemIsEditable) else: key2.setFlags(qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled) col = col + 1 self.setRowCount(line0) i = 0 for _label in self.labels: self.resizeColumnToContents(i) i = i + 1 self.sortByColumn(2, qt.Qt.AscendingOrder) for i in range(len(self.roilist)): key = str(self.item(i, 0).text()) self.roilist[i] = key self.roidict[key]['line'] = i if len(self.roilist) == 1: self.selectRow(0) else: if currentroi in self.roidict.keys(): self.selectRow(self.roidict[currentroi]['line']) _logger.debug("Qt4 ensureCellVisible to be implemented") self.building = False
[docs] def getROIListAndDict(self): """Return the currently defined ROIs, as a 2-tuple ``(roiList, roiDict)`` ``roiList`` is a list of ROI names. ``roiDict`` is a dictionary of ROI info. The ROI names must match an existing dictionary key. The name list is used to provide an order for the ROIs. The dictionary's values are sub-dictionaries containing 3 fields: - ``"from"``: x coordinate of the left limit, as a float - ``"to"``: x coordinate of the right limit, as a float - ``"type"``: type of ROI, as a string (e.g "channels", "energy") :return: ordered dict as a tuple of (list of ROI names, dict of info) """ return self.roilist, self.roidict
def _cellClickedSlot(self, *var, **kw): # selection changed event, get the current selection row = self.currentRow() col = self.currentColumn() if row >= 0 and row < len(self.roilist): item = self.item(row, 0) text = '' if item is None else str(item.text()) self.roilist[row] = text self._emitSelectionChangedSignal(row, col) def _rowChangedSlot(self, row): self._emitSelectionChangedSignal(row, 0) def _cellChangedSlot(self, row, col): _logger.debug("_cellChangedSlot(%d, %d)", row, col) if self.building: return if col == 0: self.nameSlot(row, col) else: self._valueChanged(row, col) def _valueChanged(self, row, col): if col not in [2, 3]: return item = self.item(row, col) if item is None: return text = str(item.text()) try: value = float(text) except: return if row >= len(self.roilist): _logger.debug("deleting???") return item = self.item(row, 0) if item is None: text = "" else: text = str(item.text()) if not len(text): return if col == 2: self.roidict[text]['from'] = value elif col == 3: self.roidict[text]['to'] = value self._emitSelectionChangedSignal(row, col) def nameSlot(self, row, col): if col != 0: return if row >= len(self.roilist): _logger.debug("deleting???") return item = self.item(row, col) if item is None: text = "" else: text = str(item.text()) if len(text) and (text not in self.roilist): old = self.roilist[row] self.roilist[row] = text self.roidict[text] = {} self.roidict[text].update(self.roidict[old]) del self.roidict[old] self._emitSelectionChangedSignal(row, col) def _emitSelectionChangedSignal(self, row, col): ddict = {} ddict['event'] = "selectionChanged" ddict['row'] = row ddict['col'] = col ddict['roi'] = self.roidict[self.roilist[row]] ddict['key'] = self.roilist[row] ddict['colheader'] = self.labels[col] ddict['rowheader'] = "%d" % row self.sigROITableSignal.emit(ddict)
[docs]class CurvesROIDockWidget(qt.QDockWidget): """QDockWidget with a :class:`CurvesROIWidget` connected to a PlotWindow. It makes the link between the :class:`CurvesROIWidget` and the PlotWindow. :param parent: See :class:`QDockWidget` :param plot: :class:`.PlotWindow` instance on which to operate :param name: See :class:`QDockWidget` """ sigROISignal = qt.Signal(object) def __init__(self, parent=None, plot=None, name=None): super(CurvesROIDockWidget, self).__init__(name, parent) assert plot is not None self.plot = plot self.currentROI = None self._middleROIMarkerFlag = False self._isConnected = False # True if connected to plot signals self._isInit = False self.roiWidget = CurvesROIWidget(self, name) """Main widget of type :class:`CurvesROIWidget`""" # convenience methods to offer a simpler API allowing to ignore # the details of the underlying implementation self.calculateROIs = self.calculateRois self.setRois = self.roiWidget.setRois self.getRois = self.roiWidget.getRois self.layout().setContentsMargins(0, 0, 0, 0) self.setWidget(self.roiWidget) self.visibilityChanged.connect(self._visibilityChangedHandler)
[docs] def toggleViewAction(self): """Returns a checkable action that shows or closes this widget. See :class:`QMainWindow`. """ action = super(CurvesROIDockWidget, self).toggleViewAction() action.setIcon(icons.getQIcon('plot-roi')) return action
def _visibilityChangedHandler(self, visible): """Handle widget's visibilty updates. It is connected to plot signals only when visible. """ if visible: if not self._isInit: # Deferred ROI widget init finalization self._isInit = True self.roiWidget.sigROIWidgetSignal.connect(self._roiSignal) # initialize with the ICR self._roiSignal({'event': "AddROI"}) if not self._isConnected: self.plot.sigPlotSignal.connect(self._handleROIMarkerEvent) self.plot.sigActiveCurveChanged.connect( self._activeCurveChanged) self._isConnected = True self.calculateROIs() else: if self._isConnected: self.plot.sigPlotSignal.disconnect(self._handleROIMarkerEvent) self.plot.sigActiveCurveChanged.disconnect( self._activeCurveChanged) self._isConnected = False def _handleROIMarkerEvent(self, ddict): """Handle plot signals related to marker events.""" if ddict['event'] == 'markerMoved': label = ddict['label'] if label not in ['ROI min', 'ROI max', 'ROI middle']: return roiList, roiDict = self.roiWidget.getROIListAndDict() if self.currentROI is None: return if self.currentROI not in roiDict: return x = ddict['x'] if label == 'ROI min': roiDict[self.currentROI]['from'] = x if self._middleROIMarkerFlag: pos = 0.5 * (roiDict[self.currentROI]['to'] + roiDict[self.currentROI]['from']) self.plot.addXMarker(pos, legend='ROI middle', text='', color='yellow', draggable=True) elif label == 'ROI max': roiDict[self.currentROI]['to'] = x if self._middleROIMarkerFlag: pos = 0.5 * (roiDict[self.currentROI]['to'] + roiDict[self.currentROI]['from']) self.plot.addXMarker(pos, legend='ROI middle', text='', color='yellow', draggable=True) elif label == 'ROI middle': delta = x - 0.5 * (roiDict[self.currentROI]['from'] + roiDict[self.currentROI]['to']) roiDict[self.currentROI]['from'] += delta roiDict[self.currentROI]['to'] += delta self.plot.addXMarker(roiDict[self.currentROI]['from'], legend='ROI min', text='ROI min', color='blue', draggable=True) self.plot.addXMarker(roiDict[self.currentROI]['to'], legend='ROI max', text='ROI max', color='blue', draggable=True) else: return self.calculateROIs(roiList, roiDict) self._emitCurrentROISignal() def _roiSignal(self, ddict): """Handle ROI widget signal""" _logger.debug("PlotWindow._roiSignal %s", str(ddict)) if ddict['event'] == "AddROI": xmin, xmax = self.plot.getXAxis().getLimits() fromdata = xmin + 0.25 * (xmax - xmin) todata = xmin + 0.75 * (xmax - xmin) self.plot.remove('ROI min', kind='marker') self.plot.remove('ROI max', kind='marker') if self._middleROIMarkerFlag: self.remove('ROI middle', kind='marker') roiList, roiDict = self.roiWidget.getROIListAndDict() nrois = len(roiList) if nrois == 0: newroi = "ICR" fromdata, dummy0, todata, dummy1 = self._getAllLimits() draggable = False color = 'black' else: for i in range(nrois): i += 1 newroi = "newroi %d" % i if newroi not in roiList: break color = 'blue' draggable = True self.plot.addXMarker(fromdata, legend='ROI min', text='ROI min', color=color, draggable=draggable) self.plot.addXMarker(todata, legend='ROI max', text='ROI max', color=color, draggable=draggable) if draggable and self._middleROIMarkerFlag: pos = 0.5 * (fromdata + todata) self.plot.addXMarker(pos, legend='ROI middle', text="", color='yellow', draggable=draggable) roiList.append(newroi) roiDict[newroi] = {} if newroi == "ICR": roiDict[newroi]['type'] = "Default" else: roiDict[newroi]['type'] = self.plot.getXAxis().getLabel() roiDict[newroi]['from'] = fromdata roiDict[newroi]['to'] = todata self.roiWidget.fillFromROIDict(roilist=roiList, roidict=roiDict, currentroi=newroi) self.currentROI = newroi self.calculateROIs() elif ddict['event'] in ['DelROI', "ResetROI"]: self.plot.remove('ROI min', kind='marker') self.plot.remove('ROI max', kind='marker') if self._middleROIMarkerFlag: self.plot.remove('ROI middle', kind='marker') roiList, roiDict = self.roiWidget.getROIListAndDict() roiDictKeys = list(roiDict.keys()) if len(roiDictKeys): currentroi = roiDictKeys[0] else: # create again the ICR ddict = {"event": "AddROI"} return self._roiSignal(ddict) self.roiWidget.fillFromROIDict(roilist=roiList, roidict=roiDict, currentroi=currentroi) self.currentROI = currentroi elif ddict['event'] == 'LoadROI': self.calculateROIs() elif ddict['event'] == 'selectionChanged': _logger.debug("Selection changed") self.roilist, self.roidict = self.roiWidget.getROIListAndDict() fromdata = ddict['roi']['from'] todata = ddict['roi']['to'] self.plot.remove('ROI min', kind='marker') self.plot.remove('ROI max', kind='marker') if ddict['key'] == 'ICR': draggable = False color = 'black' else: draggable = True color = 'blue' self.plot.addXMarker(fromdata, legend='ROI min', text='ROI min', color=color, draggable=draggable) self.plot.addXMarker(todata, legend='ROI max', text='ROI max', color=color, draggable=draggable) if draggable and self._middleROIMarkerFlag: pos = 0.5 * (fromdata + todata) self.plot.addXMarker(pos, legend='ROI middle', text="", color='yellow', draggable=True) self.currentROI = ddict['key'] if ddict['colheader'] in ['From', 'To']: dict0 = {} dict0['event'] = "SetActiveCurveEvent" dict0['legend'] = self.plot.getActiveCurve(just_legend=1) self.plot.setActiveCurve(dict0['legend']) elif ddict['colheader'] == 'Raw Counts': pass elif ddict['colheader'] == 'Net Counts': pass else: self._emitCurrentROISignal() else: _logger.debug("Unknown or ignored event %s", ddict['event']) def _activeCurveChanged(self, *args): """Recompute ROIs when active curve changed.""" self.calculateROIs()
[docs] def calculateRois(self, roiList=None, roiDict=None): """Compute ROI information""" if roiList is None or roiDict is None: roiList, roiDict = self.roiWidget.getROIListAndDict() activeCurve = self.plot.getActiveCurve(just_legend=False) if activeCurve is None: xproc = None yproc = None self.roiWidget.setHeader() else: x = activeCurve.getXData(copy=False) y = activeCurve.getYData(copy=False) legend = activeCurve.getLegend() idx = numpy.argsort(x, kind='mergesort') xproc = numpy.take(x, idx) yproc = numpy.take(y, idx) self.roiWidget.setHeader('ROIs of %s' % legend) for key in roiList: if key == 'ICR': if xproc is not None: roiDict[key]['from'] = xproc.min() roiDict[key]['to'] = xproc.max() else: roiDict[key]['from'] = 0 roiDict[key]['to'] = -1 fromData = roiDict[key]['from'] toData = roiDict[key]['to'] if xproc is not None: idx = numpy.nonzero((fromData <= xproc) & (xproc <= toData))[0] if len(idx): xw = xproc[idx] yw = yproc[idx] rawCounts = yw.sum(dtype=numpy.float) deltaX = xw[-1] - xw[0] deltaY = yw[-1] - yw[0] if deltaX > 0.0: slope = (deltaY / deltaX) background = yw[0] + slope * (xw - xw[0]) netCounts = (rawCounts - background.sum(dtype=numpy.float)) else: netCounts = 0.0 else: rawCounts = 0.0 netCounts = 0.0 roiDict[key]['rawcounts'] = rawCounts roiDict[key]['netcounts'] = netCounts else: roiDict[key].pop('rawcounts', None) roiDict[key].pop('netcounts', None) self.roiWidget.fillFromROIDict( roilist=roiList, roidict=roiDict, currentroi=self.currentROI if self.currentROI in roiList else None)
def _emitCurrentROISignal(self): ddict = {} ddict['event'] = "currentROISignal" _roiList, roiDict = self.roiWidget.getROIListAndDict() if self.currentROI in roiDict: ddict['ROI'] = roiDict[self.currentROI] else: self.currentROI = None ddict['current'] = self.currentROI self.sigROISignal.emit(ddict) def _getAllLimits(self): """Retrieve the limits based on the curves.""" curves = self.plot.getAllCurves() if not curves: return 1.0, 1.0, 100., 100. xmin, ymin = None, None xmax, ymax = None, None for curve in curves: x = curve.getXData(copy=False) y = curve.getYData(copy=False) if xmin is None: xmin = x.min() else: xmin = min(xmin, x.min()) if xmax is None: xmax = x.max() else: xmax = max(xmax, x.max()) if ymin is None: ymin = y.min() else: ymin = min(ymin, y.min()) if ymax is None: ymax = y.max() else: ymax = max(ymax, y.max()) return xmin, ymin, xmax, ymax
[docs] def showEvent(self, event): """Make sure this widget is raised when it is shown (when it is first created as a tab in PlotWindow or when it is shown again after hiding). """ self.raise_()