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__ = "13/11/2017"
from collections import OrderedDict
import logging
import os
import sys
import numpy
from silx.io import dictdump
from silx.utils import deprecation
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'
"""
sigROISignal = qt.Signal(object)
def __init__(self, parent=None, name=None, plot=None):
super(CurvesROIWidget, self).__init__(parent)
if name is not None:
self.setWindowTitle(name)
assert plot is not None
self.plot = plot
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)
self.currentROI = None
self._middleROIMarkerFlag = False
self._isConnected = False # True if connected to plot signals
self._isInit = False
def showEvent(self, event):
self._visibilityChangedHandler(visible=True)
qt.QWidget.showEvent(self, event)
def hideEvent(self, event):
self._visibilityChangedHandler(visible=False)
qt.QWidget.hideEvent(self, event)
@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])
[docs] def setMiddleROIMarkerFlag(self, flag=True):
"""Activate or deactivate middle marker.
This allows shifting both min and max limits at once, by dragging
a marker located in the middle.
:param bool flag: True to activate middle ROI marker
"""
if flag:
self._middleROIMarkerFlag = True
else:
self._middleROIMarkerFlag = False
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)
def _roiSignal(self, ddict):
"""Handle ROI widget signal"""
_logger.debug("CurvesROIWidget._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.plot.remove('ROI middle', kind='marker')
roiList, roiDict = self.roiTable.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.roiTable.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.roiTable.getROIListAndDict()
roiDictKeys = list(roiDict.keys())
if len(roiDictKeys):
currentroi = roiDictKeys[0]
else:
# create again the ICR
ddict = {"event": "AddROI"}
return self._roiSignal(ddict)
self.roiTable.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.roiTable.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 _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
@deprecation.deprecated(replacement="calculateRois",
reason="CamelCase convention")
def calculateROIs(self, *args, **kw):
self.calculateRois(*args, **kw)
[docs] def calculateRois(self, roiList=None, roiDict=None):
"""Compute ROI information"""
if roiList is None or roiDict is None:
roiList, roiDict = self.roiTable.getROIListAndDict()
activeCurve = self.plot.getActiveCurve(just_legend=False)
if activeCurve is None:
xproc = None
yproc = None
self.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.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.roiTable.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.roiTable.getROIListAndDict()
if self.currentROI in roiDict:
ddict['ROI'] = roiDict[self.currentROI]
else:
self.currentROI = None
ddict['current'] = self.currentROI
self.sigROISignal.emit(ddict)
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.roiTable.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 _visibilityChangedHandler(self, visible):
"""Handle widget's visibility 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.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 _activeCurveChanged(self, *args):
"""Recompute ROIs when active curve changed."""
self.calculateRois()
[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)
"""Deprecated signal for backward compatibility with silx < 0.7.
Prefer connecting directly to :attr:`CurvesRoiWidget.sigRoiSignal`
"""
def __init__(self, parent=None, plot=None, name=None):
super(CurvesROIDockWidget, self).__init__(name, parent)
assert plot is not None
self.plot = plot
self.roiWidget = CurvesROIWidget(self, name, plot=plot)
"""Main widget of type :class:`CurvesROIWidget`"""
# convenience methods to offer a simpler API allowing to ignore
# the details of the underlying implementation
# (ALL DEPRECATED)
self.calculateROIs = self.calculateRois = self.roiWidget.calculateRois
self.setRois = self.roiWidget.setRois
self.getRois = self.roiWidget.getRois
self.roiWidget.sigROISignal.connect(self._forwardSigROISignal)
self.currentROI = self.roiWidget.currentROI
self.layout().setContentsMargins(0, 0, 0, 0)
self.setWidget(self.roiWidget)
def _forwardSigROISignal(self, ddict):
# emit deprecated signal for backward compatibility (silx < 0.7)
self.sigROISignal.emit(ddict)
[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
[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_()
qt.QDockWidget.showEvent(self, event)