Commit 76006110 authored by Carsten Richter's avatar Carsten Richter

Merge branch 'update-intensity' into 'master'

Update intensity view widget

Closes #12, #20, and #21

See merge request !64
parents 040eb476 6572fc01
Pipeline #4214 failed with stages
in 3 minutes and 14 seconds
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2018 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.
#
# ###########################################################################*/
"""XSocs widgets"""
from __future__ import absolute_import
import logging
_logger = logging.getLogger(__name__)
# Import PyQt5 before importing silx.gui
try:
import PyQt5.QtCore # Forces silx to use PyQt5
except ImportError:
except ImportError as e:
_logger.error("Cannot load PyQt5: PyQt5 is required to run XSocs")
raise e
from silx.gui import colors as _colors
# Set colormaps available from silx colormap dialog
_colors.setPreferredColormaps(
['viridis', 'magma', 'inferno', 'plasma', 'jet', 'afmhot', 'gray'])
from .gui import (xsocs_main,
merge_window,
......
......@@ -52,6 +52,7 @@ from .RectRoiWidget import RectRoiWidget
from ..shift.ShiftView import ShiftWidget
from ...widgets.XsocsPlot2D import XsocsPlot2D
from ...widgets.ScatterPlot import ScatterPlot
from ...widgets.Buttons import FixedSizePushButon
......@@ -338,8 +339,6 @@ class EntriesTable(Qt.QTableWidget):
class IntensityView(Qt.QMainWindow):
sigProcessApplied = Qt.Signal(object)
plot = property(lambda self: self.__plotWindow)
def __init__(self,
intensityGroup,
parent=None,
......@@ -365,13 +364,11 @@ class IntensityView(Qt.QMainWindow):
self.setWindowTitle('[XSOCS] {0}:{1}'.format(intensityGroup.filename,
intensityGroup.path))
self.__selectedPoint = None
self.__shiftApplied = False
self.__plotWindow = plotWindow = XsocsPlot2D()
plotWindow.setShowMousePosition(True)
plotWindow.setShowSelectedCoordinates(True)
plotWindow.sigPointSelected.connect(self.__slotPointSelected)
self.__scatterPlot = ScatterPlot()
self.__scatterPlot.sigSelectedIndexChanged.connect(
self.__updateProfile)
self.__iGroup = intensityGroup
......@@ -448,7 +445,7 @@ class IntensityView(Qt.QMainWindow):
# ROI widget
rectRoiWidget = RectRoiWidget(plot=plotWindow)
rectRoiWidget = RectRoiWidget(plot=self.__scatterPlot)
self.__roiManager = rectRoiWidget.roiManager()
......@@ -459,7 +456,12 @@ class IntensityView(Qt.QMainWindow):
# Profile widget
profileWid = Qt.QWidget()
# Override sizeHint to avoid profile being too high
class ProfileWidget(Qt.QWidget):
def sizeHint(self):
return Qt.QSize(640, 300)
profileWid = ProfileWidget()
profileLayout = Qt.QHBoxLayout(profileWid)
self.__profilePlot = XsocsPlot2D()
......@@ -480,7 +482,7 @@ class IntensityView(Qt.QMainWindow):
dock.setFeatures(features)
self.addDockWidget(area, dock)
self.setCentralWidget(plotWindow)
self.setCentralWidget(self.__scatterPlot)
self.__update()
......@@ -628,10 +630,10 @@ class IntensityView(Qt.QMainWindow):
if normalizer is not None:
title = ' '.join((title, '/', normalizer))
self.__plotWindow.setGraphTitle(title)
self.__scatterPlot.setGraphTitle(title)
if not entries: # Nothing to show
self.__plotWindow.clear()
self.__scatterPlot.clear()
else: # Use positions of first checked entry
......@@ -649,30 +651,24 @@ class IntensityView(Qt.QMainWindow):
for entry in entries:
intensity += self.__computeIntensity(entry, normalizer)
self.__plotWindow.setPlotData(pos0, pos1, intensity)
self.__plotWindow.setGraphTitle(title)
def __slotPointSelected(self, point):
"""Slot called when a point is selected on the intensity map.
:param point:
"""
if point is None:
return
self.__selectedPoint = point
self.__updateProfile()
previousScatter = self.__scatterPlot.getScatter()
self.__scatterPlot.addScatter(pos0, pos1, intensity, symbol='s')
if previousScatter is None:
# Only reset zoom when no scatter plot was displayed
self.__scatterPlot.resetZoom()
self.__scatterPlot.setGraphTitle(title)
def __updateProfile(self):
"""Update the profile plot to reflect current status"""
# Clear the plot
plot = self.__profilePlot
plot.setGraphTitle()
plot.getXAxis().setLabel('')
plot.getYAxis().setLabel('')
plot.clear()
if self.__selectedPoint is None:
self.__profilePlot.setGraphTitle()
self.__profilePlot.getXAxis().setLabel('')
self.__profilePlot.getYAxis().setLabel('')
self.__profilePlot.clear()
selectedIndex = self.__scatterPlot.getSelectedIndex()
selectedPosition = self.__scatterPlot.getSelectedPosition()
if selectedIndex is None or selectedPosition is None:
return
entries = self.__entriesTable.getEntries()
......@@ -688,8 +684,7 @@ class IntensityView(Qt.QMainWindow):
for entryIdx, entry in enumerate(entries):
item = self.__iGroup.getIntensityItem(entry)
intensities[entryIdx] = item.getPointValue(
self.__selectedPoint.xIdx)
intensities[entryIdx] = item.getPointValue(selectedIndex)
xValue = self.__iGroup.xsocsH5.positioner(entry, positionerName)
xData[entryIdx] = xValue if xValue is not None else entryIdx
......@@ -700,29 +695,29 @@ class IntensityView(Qt.QMainWindow):
logger.error(
'Cannot get normalization for entry %s, set to 1' % entry)
else:
intensities[entryIdx] /= norm[self.__selectedPoint.xIdx]
intensities[entryIdx] /= norm[selectedIndex]
# Set the plot
plot.setGraphTitle(
'Profile @ ({0:.7g}, {1:.7g})'.format(self.__selectedPoint.x,
self.__selectedPoint.y))
self.__profilePlot.setGraphTitle(
'Profile @ ({0:.7g}, {1:.7g})'.format(*selectedPosition))
plot.getXAxis().setLabel(positionerName)
self.__profilePlot.getXAxis().setLabel(positionerName)
yLabel = 'Intensity'
if normalizerName:
yLabel = ' / '.join((yLabel, normalizerName))
plot.getYAxis().setLabel(yLabel)
self.__profilePlot.getYAxis().setLabel(yLabel)
plot.addCurve(xData,
intensities,
legend='All',
symbol='o',
color='blue')
self.__profilePlot.addCurve(
xData,
intensities,
legend='All',
symbol='o',
color='blue')
if selectedIndices:
plot.addCurve(
self.__profilePlot.addCurve(
xData[selectedIndices],
intensities[selectedIndices],
legend='Selected ({0}/{1})'.format(
......
......@@ -56,11 +56,10 @@ class PointWidget(Qt.QFrame):
layout.addWidget(yEdit)
def setPoint(self, x, y):
"""Sets the values to display.
:param Union[float,None] x:
:param Union[float,None] y:
"""
Sets the values to display.
:param x:
:param y:
:return:
"""
self.__xEdit.setText('{0:6g}'.format(x))
self.__yEdit.setText('{0:6g}'.format(y))
self.__xEdit.setText('' if x is None else '{0:6g}'.format(x))
self.__yEdit.setText('' if y is None else '{0:6g}'.format(y))
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2018 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.
#
# ###########################################################################*/
"""Plot widget used to display scatter plot in XSocs"""
from __future__ import absolute_import, division
import logging
_logger = logging.getLogger(__name__)
from silx.gui import qt
from silx.gui.plot import PlotWidget
from silx.gui.plot import tools, actions, PlotToolButtons
from silx.gui.plot.ColorBar import ColorBarWidget
from ..widgets.PointWidget import PointWidget
class ScatterPlot(PlotWidget):
"""PlotWidget displaying a scatter plot"""
sigSelectedIndexChanged = qt.Signal()
"""Signal emitted when the selected point has changed"""
def __init__(self, parent=None, backend='mpl'):
super(ScatterPlot, self).__init__(parent, backend)
self.__selectedIndex = None
# Set defaults
self.setDataMargins(.1, .1, .1, .1)
self.setKeepDataAspectRatio(True)
self.getDefaultColormap().setName('jet')
# Add toolbars
self.addToolBar(
tools.InteractiveModeToolBar(parent=self, plot=self))
toolbar = qt.QToolBar()
toolbar.addAction(
actions.control.ResetZoomAction(parent=self, plot=self))
toolbar.addWidget(
PlotToolButtons.AspectToolButton(parent=self, plot=self))
toolbar.addAction(
actions.control.ColormapAction(parent=self, plot=self))
toolbar.addWidget(
PlotToolButtons.SymbolToolButton(parent=self, plot=self))
self.addToolBar(toolbar)
self.addToolBar(
tools.OutputToolBar(parent=self, plot=self))
# Add Colorbar
colorBar = ColorBarWidget(parent=self, plot=self)
# Make ColorBarWidget background white by changing its palette
colorBar.setAutoFillBackground(True)
palette = colorBar.palette()
palette.setColor(qt.QPalette.Background, qt.Qt.white)
palette.setColor(qt.QPalette.Window, qt.Qt.white)
colorBar.setPalette(palette)
# Add colorbar to central widget
gridLayout = qt.QGridLayout()
gridLayout.setSpacing(0)
gridLayout.setContentsMargins(0, 0, 0, 0)
gridLayout.addWidget(self.getWidgetHandle(), 0, 0)
gridLayout.addWidget(colorBar, 0, 1)
gridLayout.setRowStretch(0, 1)
gridLayout.setColumnStretch(0, 1)
centralWidget = qt.QWidget(self)
centralWidget.setLayout(gridLayout)
self.setCentralWidget(centralWidget)
# Add mouse/selected point position info
widget = qt.QWidget()
layout = qt.QFormLayout(widget)
layout.setFormAlignment(qt.Qt.AlignLeft)
layout.setFieldGrowthPolicy(qt.QFormLayout.FieldsStayAtSizeHint)
layout.setContentsMargins(0, 0, 0, 0)
self.__mousePointWidget = PointWidget()
self.__mousePointWidget.setFrameStyle(qt.QFrame.Box)
layout.addRow('Mouse', self.__mousePointWidget)
self.__selectedPointWidget = PointWidget()
self.__selectedPointWidget.setFrameStyle(qt.QFrame.Box)
layout.addRow('Selected', self.__selectedPointWidget)
dock = qt.QDockWidget('Position Information')
dock.setFeatures(qt.QDockWidget.NoDockWidgetFeatures)
dock.setWidget(widget)
self.addDockWidget(qt.Qt.BottomDockWidgetArea, dock)
# Connect to mouse events
self.sigPlotSignal.connect(self.__plotWidgetEvents)
self.sigContentChanged.connect(self.__plotWidgetContentChanged)
def __plotWidgetEvents(self, event):
"""Handle PlotWidget mouse events"""
if event['event'] == 'mouseClicked': # Handle selection
self.selectPoint(event['x'], event['y'])
if event['event'] == 'mouseMoved': # Display mouse coordinates
self.__mousePointWidget.setPoint(event['x'], event['y'])
def __plotWidgetContentChanged(self, action, kind, legend):
"""Handle PlotWidget content changed"""
if kind == 'scatter':
# Refresh selection
self.setSelectedIndex(self.getSelectedIndex())
def selectPoint(self, x, y):
"""Select closest scatter plot point and draws a crosshair on it.
:param float x: X data coordinate
:param float y: Y data coordinate
"""
scatter = self.getScatter()
if scatter is None:
self.setSelectedIndex(None)
return
xData = scatter.getXData(copy=False)
yData = scatter.getYData(copy=False)
if len(xData) == 0:
self.setSelectedIndex(None)
return
index = ((xData - x) ** 2 + (yData - y) ** 2).argmin()
self.setSelectedIndex(index)
def setSelectedIndex(self, index):
"""Select a point in the scatter plot by its index
:param Union[None,int] index:
The index of the selected point in the scatter plot
"""
if index != self.__selectedIndex:
if index is not None:
scatter = self.getScatter()
if scatter is None:
_logger.warning(
"Cannot select index without a scatter plot")
index = None
else:
if index >= len(scatter.getXData(copy=False)):
_logger.warning("Selected index is out of range")
index = None
self.__selectedIndex = index
if index is not None:
x, y = self.getSelectedPosition()
self.addXMarker(
x, legend='__selected_x_marker__', color='pink')
self.addYMarker(
y, legend='__selected_y_marker__', color='pink')
self.__selectedPointWidget.setPoint(x, y)
else:
self.remove(legend='__selected_x_marker__', kind='marker')
self.remove(legend='__selected_y_marker__', kind='marker')
self.__selectedPointWidget.setPoint(x=None, y=None)
self.sigSelectedIndexChanged.emit()
def getSelectedIndex(self):
"""Returns selected point index in the scatter plot or None
:rtype: Union[None,int]
"""
if self.__selectedIndex is None:
return None
scatter = self.getScatter()
if (scatter is None or
self.__selectedIndex >= len(scatter.getXData(copy=False))):
# Selected index is not sync with plot content
# This should not happen
self.setSelectedIndex(None)
return None
return self.__selectedIndex
def getSelectedPosition(self):
"""Returns the position of the selected point
:return: (x, y) position in data coordinates or
None if there is no selection
:rtype: Union[None,List[float]]
"""
index = self.getSelectedIndex()
scatter = self.getScatter()
if index is None or scatter is None:
return None
x = scatter.getXData(copy=False)[index]
y = scatter.getYData(copy=False)[index]
return x, y
......@@ -489,6 +489,7 @@ class XsocsPlot2D(PlotWindow):
self.setActiveCurveHandling(False)
self.setKeepDataAspectRatio(True)
self.setDataMargins(.02, .02, .02, .02)
self.__selectedPoint = None
self.__snapToPoint = True
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment